shle2821
shle2821

Reputation: 1886

Recursively splitting a list into two using LISP

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.

Upvotes: 3

Views: 5617

Answers (7)

Ash
Ash

Reputation: 261

(defun split-list (L)
    (if (endp L)
    '(nil nil)
    (let ((X (split-list (cdr L))))
        (list (cons (car L) (cadr X)) (car X))
)))

Upvotes: 0

Stefan
Stefan

Reputation: 28581

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.

Upvotes: -1

Will Ness
Will Ness

Reputation: 71119

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)))

Upvotes: 0

Rainer Joswig
Rainer Joswig

Reputation: 139411

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))))

Upvotes: 1

Joshua Taylor
Joshua Taylor

Reputation: 85913

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)))))

Upvotes: 7

abo-abo
abo-abo

Reputation: 20362

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.

Upvotes: 2

uselpa
uselpa

Reputation: 18937

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)))

Upvotes: 1

Related Questions