3

Using LISP, i need to create a function that splits a list into two lists. The first list consists of 1st, 3rd, 5th, 7th, etc elements and the second list consists of 2nd, 4th, 6th, etc elements.

Output Examples:

(SPLIT-LIST ( )) => (NIL NIL)

(SPLIT-LIST '(A B C D 1 2 3 4 5)) => ((A C 1 3 5) (B D 2 4))

(SPLIT-LIST '(B C D 1 2 3 4 5)) => ((B D 2 4) (C 1 3 5))

(SPLIT-LIST '(A)) => ((A) NIL)

The function need to be recursive.

This is my code so far.

(defun SPLIT-LIST (L)
  (cond
    ((null L) NIL)
    ((= 1 (length L)) (list (car L)))
    (t (cons (cons (car L) (SPLIT-LIST (cddr L))) (cadr L)))))
);cond
);defun

i'm going to try to use flatten later on so that i end up w/ two lists, but for now, i just can't seem to get the sequence correctly.

MY CODE:

> (SPLIT-LIST '(1 2 3 4))
((1 (3) . 4) . 2)

I just can't seem to make the code print 1 3 2 4 instead of 1 3 4 2.

> (SPLIT-LIST '(1 2 3 4 5 6))
((1 (3 (5) . 6) . 4) . 2)

I can't make the second half of the expected output to print in the correct sequence.

shle2821
  • 1,856
  • 1
  • 19
  • 26
  • 1
    Notice that your code doesn't implement the first two `cond` clauses (the base cases) correctly. Fix those first, then continue with the last clause. – C. K. Young Mar 12 '14 at 20:46

7 Answers7

7

Your code

We typically read Lisp code by indentation, and don't write in all-caps. Since we read by indentation, we don't need to put closing parens (or any parens, really) on their own line. Your code, properly formatted, then, is:

(defun split-list (l)
  (cond
    ((null l) '())
    ((= 1 (length l)) (list (car l)))
    (t (cons (cons (car l)
                   (split-list (cddr l)))
             (cadr l)))))

Getting the base cases right

Split-list is always supposed to return a list of two lists. We should cover those base cases first. When l is empty, then there's nothing in the left list or the right, so we can simply return '(() ()). Then first condition then becomes:

((null l) '(() ())) ; or ((endp l) '(() ()))

Judging by your second case, I gather that you want the second and third cases to be: (i) if there's only one element left, it must be an odd-numbered one, and belongs in the left result; (ii) otherwise, there are at least two elements left, and we can add one to each. Then the second condition should be

((= 1 (length l)) (list (car l) '()))

It's actually kind of expensive to check the length of l at each step. You only care whether there is only one element left. You already know that l isn't empty (from the first case), so you can just check whether the rest oflis the empty list. I find it more readable to usefirstandrest` when working with cons cells as lists, so I'd write the second clause as:

((endp (rest l)) (list (list (first l)) '()))

Handling the recursive case

Now, your final case is where there are at least two elements. That means that l looks like (x y . zs). What you need to do is call split-list on zs to get some result of the form (odd-zs even-zs), and then take it apart and construct ((x . odd-zs) (y . even-zs)). That would look something like this:

(t (let ((split-rest (split-list (rest (rest l)))))
     (list (list* (first l) (first split-rest))
           (list* (second l) (second split-rest)))))

There are actually some ways you can clean that up. We can use destructuring-bind to pull odd-zs and even-zs out at the same time. Since this is the last clause of the cond, and a clause returns the value of the test if there are no body forms, we don't need the initial t. The last clause can be:

((destructuring-bind (odd-zs even-zs)            ; *
       (split-list (rest (rest l)))
     (list (list* (first l) odd-zs)
           (list* (second l) even-zs))))))

*I omitted the t test because if a cond clause has no body forms, then the value of the test is returned. That works just fine here.

Putting that all together, we've reworked your code into

(defun split-list (l)
  (cond
    ((endp l) '(() ()))
    ((endp (rest l)) (list (list (first l)) '()))
    ((destructuring-bind (odd-zs even-zs) 
         (split-list (rest (rest l)))
       (list (list* (first l) odd-zs)
             (list* (second l) even-zs))))))
CL-USER> (split-list '(a b c 1 2 3))
((A C 2) (B 1 3))
CL-USER> (split-list '(a b c d 1 2 3))
((A C 1 3) (B D 2))

Other approaches

I think it's worth exploring some approaches that are tail recursive, as an implementation that supports tail call optimization can convert them to loops. Tail recursive functions in Common Lisp are also typically easy to translate into do loops, which are much more likely to be implemented as iteration. In these solutions, we'll build up the result lists in reverse, and then reverse them when it's time to return them.

Recursing one element at a time

If the left and right slices are interchangeable

If it doesn't matter which of the two lists is first, you can use something like this:

(defun split-list (list &optional (odds '()) (evens '()))
  (if (endp list)
      (list (nreverse odds)
            (nreverse evens))
      (split-list (rest list)
                  evens
                  (list* (first list) odds))))
CL-USER> (split-list '(a b c 1 2 3))
((A C 2) (B 1 3))
CL-USER> (split-list '(a b c d 1 2 3))
((B D 2) (A C 1 3))

This can actually be written very concisely using a do loop, but that's typically seen as iterative, not recursive:

(defun split-list (list)
  (do ((list list (rest list))
       (odds '() evens)
       (evens '() (list* (first list) odds)))
      ((endp list) (list (nreverse odds) (nreverse evens)))))
If they're not interchangable

If you always need the list containing the first element of the original list to be first, you'll need a little bit more logic. One possibility is:

(defun split-list (list &optional (odds '()) (evens '()) (swap nil))
  (if (endp list)
      (if swap 
          (list (nreverse evens)
                (nreverse odds))
          (list (nreverse odds)
                (nreverse evens)))
      (split-list (rest list)
                  evens
                  (list* (first list) odds)
                  (not swap))))
CL-USER> (split-list '(a b c 1 2 3))
((A C 2) (B 1 3))
CL-USER> (split-list '(a b c d 1 2 3))
((A C 1 3) (B D 2))

I think that (if swap … …) is actually a bit ugly. We can use cond so that we can get multiple forms (or if and progn), and swap the values of odds and evens before returning. I think this is actually a bit easier to read, but if you're aiming for a pure recursive solution (academic assignment?), then you might be avoiding mutation, too, so rotatef wouldn't be available, and using a when just to get some side effects would probably be frowned upon.

(defun split-list (list &optional (odds '()) (evens '()) (swap nil))
  (cond
    ((endp list)
     (when swap (rotatef odds evens))
     (list (nreverse odds) (nreverse evens)))
    ((split-list (rest list)
                 evens
                 (list* (first list) odds)
                 (not swap)))))

This lends itself to do as well:

(defun split-list (list)
  (do ((list list (rest list))
       (odds '() evens)
       (evens '() (list* (first list) odds))
       (swap nil (not swap)))
      ((endp list) 
       (when swap (rotatef odds evens))
       (list (nreverse odds) (nreverse evens)))))

Recursing two elements at a time

Another more direct approach would recurse down the list by cddr (i.e., (rest (rest …))) and add elements to the left and right sublists on each recursion. We need to be a little careful that we don't accidentally add an extra nil to the right list when there are an odd number of elements in the input, though.

(defun split-list (list &optional (left '()) (right '()))
  (if (endp list)
      (list (nreverse left)
            (nreverse right))
      (split-list (rest (rest list))
                  (list* (first list) left)
                  (if (endp (rest list))
                      right
                      (list* (second list) right)))))
CL-USER> (split-list '(a b c 1 2 3))
((A C 2) (B 1 3))
CL-USER> (split-list '(a b c d 1 2 3))
((A C 1 3) (B D 2))

And again, a do version:

(defun split-list (list)
  (do ((list list (rest (rest list)))
       (left '() (list* (first list) left))
       (right '() (if (endp (rest list)) right (list* (second list) right))))
      ((endp list) (list (nreverse left) (nreverse right)))))
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • thank you!! not only was the answer helpful, but all the explanation was just superb as well!! thanks a lot!! – shle2821 Mar 12 '14 at 23:48
  • hey just what's the definitive difference between method list and list*? I reverse engineered it and have a vague idea the diff is, but just want to know for future reference what the exact diff is. Thank you!!! – shle2821 Mar 13 '14 at 05:50
  • @shle2821 Reverse engineering's a great way to learn, although for future reference, it's a good idea to check the actual reference, i.e., the [HyperSpec entry on LIST, LIST*](http://www.lispworks.com/documentation/HyperSpec/Body/f_list_.htm). I'll assume that we know what `list` does. `list*` is list `cons` on two arguments (`(list* a b) == (a . b)`, so `(list* a '(b c)) == (a b c))`), but can also accept more (`(list* a b c) == (a b . c)`, so `(list* a b '(c d)) == (a b c d))`) or less, but requires at least one, (`(list* a) == a`). – Joshua Taylor Mar 13 '14 at 11:49
  • @shle2821 `list*` is actually the more general tool, since `(list ...) == (list* ... '())`. `(list* args...)` can be trivially implemented as `(reduce 'cons args :from-end t)`. – Joshua Taylor Mar 13 '14 at 11:51
2

Here's what I've got:

(defun split-list (lst)
  (if lst
      (if (cddr lst)
          (let ((l (split-list (cddr lst))))
            (list
             (cons (car lst) (car l))
             (cons (cadr lst) (cadr l))))
        `((,(car lst)) ,(cdr lst)))
    '(nil nil)))

After reading SICP I'm rarely confused about recursion. I highly recommend it.

abo-abo
  • 20,038
  • 3
  • 50
  • 71
  • That's not so cool, since `length` has to traverse the entire list each time. – Svante Mar 12 '14 at 21:40
  • 1
    Premature optimization. First get the function to work correctly. Then, if you care about performance and you know that your `length` implementation is O(n), you optimize. – abo-abo Mar 12 '14 at 21:51
  • elisp even does have arrays https://www.gnu.org/software/emacs/manual/html_node/elisp/Arrays.html wow! – Display Name Mar 12 '14 at 22:14
  • Also hashes. Bignums and rationals through calc. Is that a surprise for you? – abo-abo Mar 12 '14 at 22:18
  • @abo-abo, hey i tried reverse engineering your code, and I was wondering what the specific purpose of backquote on : `((,(car lst)) ,(cdr lst))) is. ` <- this sign left next to 1 key. I tried searching online and just couldn't find a crystal clear explanation. Thanks – shle2821 Mar 13 '14 at 05:57
  • backquote is somehow removed from my comment above. FYI. – shle2821 Mar 13 '14 at 05:58
  • Backquote makes things a bit shorter: compare to `(list (list (car lst)) (cdr lst))`. – abo-abo Mar 13 '14 at 07:12
  • @shle2821 use double-backticks to enclose code with a backtick: ``{ `((,(car lst)) ,(cdr lst))) }``. – Will Ness Mar 13 '14 at 09:18
1

Here's my take, using an inner function:

(defun split-list (lst)
  (labels ((sub (lst lst1 lst2 flip)
             (if lst
               (if flip
                 (sub (cdr lst) (cons (car lst) lst1) lst2 (not flip))
                 (sub (cdr lst) lst1 (cons (car lst) lst2) (not flip)))
               (list (reverse lst1) (reverse lst2)))))
    (sub lst nil nil t)))
uselpa
  • 18,732
  • 2
  • 34
  • 52
1

As a Common Lisp LOOP:

(defun split-list (list)

  "splits a list into ((0th, 2nd, ...) (1st, 3rd, ...))"

  (loop for l = list then (rest (rest l))

        until (null l)                     ; nothing to split

        collect (first l) into l1          ; first split result

        unless (null (rest l))
        collect (second l) into l2         ; second split result

        finally (return (list l1 l2))))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
0

With internal tail-recursive function building the lists in top-down manner (no reverses, loop code probably compiles to something equivalent), with a head-sentinel trick (for simplicity).

(defun split-list (lst &aux (lst1 (list 1)) (lst2 (list 2)))
  (labels ((sub (lst p1 p2)
             (if lst 
                 (progn (rplacd p1 (list (car lst))) 
                        (sub (cdr lst) p2 (cdr p1)))
                 (list (cdr lst1) (cdr lst2)))))
    (sub lst lst1 lst2)))
Will Ness
  • 70,110
  • 9
  • 98
  • 181
0
(defun split-list (L)
    (if (endp L)
    '(nil nil)
    (let ((X (split-list (cdr L))))
        (list (cons (car L) (cadr X)) (car X))
)))
Ash
  • 261
  • 4
  • 16
-1

Flatten is fun to define in Lisp. But I've never had a use for it. So if you think "I could use flatten to solve this problem" it's probably because you're trying to solve the wrong problem.

Stefan
  • 27,908
  • 4
  • 53
  • 82