Using Common Lisp, I want to split a list into two parts, in which the first resulting part has a fixed size (say 20 elements) and the rest, well, contains the rest of the list. This should also work when the list is actually shorter than the limit, which means I can't use subseq. The typically approach I would have chosen would involve the two traditional functions for doing something iteratively in CL, i.e. do, dotimes or dolist. However, this is ugly, and for the sake of learning something new, I had a look at how to solve the problem with loop.
This is the first solution I came up with:
(let ((lst (list 'a 'b 'c 'd))
(limit 2))
(loop for elem in lst
for rest on lst
collect elem into result
count elem into i
while (< i limit)
finally (return (values result (rest rest)))))
This returns
(a b) (cd).
We iterate over all elements of the list, collecting them into the first part (result). In parallel (not really), I'll maintain the current rest of the list. If I reach the limit, I return the result. Calling both #car and #cdr on the same list just to keep both parts seems not really a good way to go, so I had a look at iterate. A translation of the loop code is straight-forward:
(let ((lst (list 'a 'b 'c 'd))
(limit 2))
(iter (for elem in lst)
(for rest on lst)
(collect elem into result)
(count elem into i)
(while (< i limit))
(finally (return (values result (rest rest))))))
But iterate has generators, which I think might be used to do have a look at the rest only at the end:
(let ((lst (list 'a 'b 'c 'd))
(limit 2))
(iter (for elem in lst)
(generate rest on lst)
(collect elem into result)
(count elem into i)
(while (<= i limit))
(when (= i limit) (next rest))
(finally (return (values result (rest rest))))))
This won't work however: calling next that late only means I will generate the cdr of the initial list, i.e., this returns
(a b) (b c d).
Back to loop, I found a different approach:
(time (let ((lst)
(limit 99000))
(dotimes (i 100000)
(setq lst (append (list i) lst)))
(loop
for rest on lst
collect (first rest) into result
count rest into i
while (< i limit)
finally (return (values result (rest rest))))
t))
Instead of doing two things seperately, I now use the result of one operation (keeping the rest) for the other (computing the first part). It turns out that this is also faster than my first approach.