First, when you have cond
with only one test and a default t
clause, please use if
instead.
Also, you are using first
, but cadr
; second
is more readable in your context than cadr
.
Now, the order is swapped for even lists. Try to perform a step-by-step execution. It might be a little tedious by hand but this is useful to understand what happens. I personally prefer to use the trace
macro: (trace split-list)
. Then, running your example:
0: (split-list (a b c 1 2 3))
1: (split-list (b c 1 2 3))
2: (split-list (c 1 2 3))
3: (split-list (1 2 3))
4: (split-list (2 3))
5: (split-list (3))
6: (split-list nil)
6: split-list returned (nil nil)
5: split-list returned ((3) nil)
4: split-list returned ((3) (2))
3: split-list returned ((1 3) (2))
2: split-list returned ((1 3) (c 2))
1: split-list returned ((b 1 3) (c 2))
0: split-list returned ((b 1 3) (a c 2))
Unclear? Try with an odd-sized list:
0: (split-list (a b c 1 2))
1: (split-list (b c 1 2))
2: (split-list (c 1 2))
3: (split-list (1 2))
4: (split-list (2))
5: (split-list nil)
5: split-list returned (nil nil)
4: split-list returned ((2) nil)
3: split-list returned ((2) (1))
2: split-list returned ((c 2) (1))
1: split-list returned ((c 2) (b 1))
0: split-list returned ((a c 2) (b 1))
It seems you always store the innermost result in the left list!
A possible recursive implementation goes roughly like this:
(defun split-list (list)
(if (endp list)
'(nil nil)
(destructuring-bind (left right) (split-list (cddr list))
(list (cons (first list) left)
(if (second list)
(cons (second list) right)
right)))))
But this can blow the stack for sufficiently large inputs. For your information, here is a simple non-recursive approach with loop
:
(defun split-list (list)
(loop for (a b) on list by #'cddr
collect a into left
when b
collect b into right
finally (return (list left right)))
And since you probably will have to split your list into more than 2 lists in your next assignment, a more generic version, still with loop:
(defun split-list (list &optional (n 2))
(loop with a = (make-array n :initial-element nil)
for e in list
for c = 0 then (mod (1+ c) n)
do (push e (aref a c))
finally (return (map 'list #'nreverse a))))
(split-list '(a b c d e f g) 3)
=> ((a d g) (b e) (c f))
If you want to have fun with circular lists, you can also try this, which works for any sequence, not only lists:
(defun split-n (sequence &optional (n 2))
(let* ((ring (make-list n :initial-element nil))
(head ring)
(last (last ring)))
(setf (cdr last) ring)
(map nil
(lambda (u)
(push u (first ring))
(pop ring))
sequence)
(setf (cdr last) nil)
(map-into head #'nreverse head)))
If you plan to investigate how this works, evaluate (setf *print-circle* t)
first.
length
on each recursive call is not a good idea. It has to traverse the the listl
each time. – Knavery[(juxt (op select @1 (range 0 : 2)) (op select @1 (range 1 : 2))) '(a b c 1 2 3)] -> ((a c 2) (b 1 3))
. – Ret(transpose (tuples 2 '(a b c 1 2 3)))
.tuples
with an argument of2
shores up consecutive pairs within the list into((a b) (c 1) (2 3))
. Thentranspose
does exactly what we want (just think of this list of pairs as the columns of a matrix). – Ret