» looping issues

Tuesday, 6 April A.D. 2010 @ 11:37 PM

I really enjoy using loop. Collapsing a surrounding let in a with binding clause or lifting an if expression into a conditional execution clause brings me happiness. One of the things I dislike about it, though, is collecting with nested loops. In highly repetitive macroized code, for instance, it's not unusual to want to do something like:

(loop for ...
  do (loop for ...
        collect ...))

Except that doesn't work, because you're only collecting in the inner loop, not in the outer loop where you want. There are ways around this; you can do:

(loop with list = nil
  for ...
  do (loop for ...
       do (push ... list))
  finally (return (nreverse list)))

Or, if you like using loop's features:

(loop for ...
  nconc (loop for ...
           collect ...))

I don't particularly care for either of those, though I'll use the last one if the situation arises.

Recently, though, I found a shortcut for a special case. I needed to loop over several quantities, each of which could be true or false. Extending the second workaround to a multilevel loop...well, let's not go there. However, by doing some bit-twiddling, I arrived at:

(loop for bits from 0 below (ash 1 n-quantities)
  for q0 = (logbitp 0 bits)
  for q1 = (logbitp 1 bits)
  for q2 = (logbitp 2 bits)
  ...
  collect ...)

which works well and avoids deeply nesting code. It even works if you have one quantity that iterates over a number of values that's not a power of two. Say you have three true/false things and one tri-valued thing:

(loop for bits from 0 upto #b10111
  for q0 = (logbitp 0 bits)
  for q1 = (logbitp 1 bits)
  for q2 = (logbitp 2 bits)
  for q3 = (ecase (ldb (byte 2 3) bits)
             (0 ...)
             (1 ...)
             (2 ...))
  ...)

Perhaps not overly useful, but a nice trick to have in your bag.