简体   繁体   中英

Splitting a list recursively in Scheme

What I want to do is define a list such as (define lst '(1 2 3 4 5 6)) and then call (split lst) which will return '((1 3 5) (2 4 6)).

Some examples:

  • When lst is '(1 2 3 4 5 6) it should return '((1 3 5) (2 4 6))
  • When lst is '(1 2 3 4 5 6 7) it should return '((1 3 5 7) (2 4 6))
  • When lst is '("a" "little" "bit" "of" "that" "to" "spice" "things" "up") it should return '(("a" "bit" "that" "spice" "up") ("little" "of" "to" "things"))

It should alternate when building the two lists. So the first index should go in the first list, second index in the second list, third index in the first list, etc.

Here is my current script.

(define (split lst)
  (cond ((null? lst) lst)
        ((null? (cdr lst)) lst)
        ((cons (cons (car lst) (split (cddr lst))) (cons (cadr lst) (split (cddr lst)))))))

Currently, this is what outputs when I split the list '(1 2 3 4 5 6)

((1 (3 (5) 6) 4 (5) 6) 2 (3 (5) 6) 4 (5) 6)

Lets fix your code step by step:

(define (split lst)
  (cond ((null? lst) lst)
        ((null? (cdr lst)) lst)
        ((cons (cons (car lst) (split (cddr lst))) (cons (cadr lst) (split (cddr lst)))))))

The first thing I notice is the lack of an else in the last case of the cond . Conds are supposed to look like:

(cond (question-1 answer-1)
      (question-2 answer-2)
      ...
      (else else-answer))

With an else inserted your code looks like this:

(define (split lst)
  (cond ((null? lst) lst)
        ((null? (cdr lst)) lst)
        (else
         (cons (cons (car lst) (split (cddr lst))) (cons (cadr lst) (split (cddr lst)))))))

The next thing is the first base case, or the answer to the (null? lst) cond question. On an empty list what should it return?

It seems like no matter how long the list is, it should always return a list of exactly two inner lists. So when lst is empty the logical answer would be (list '() '()) .

(define (split lst)
  (cond ((null? lst) 
         (list '() '()))
        ((null? (cdr lst)) lst)
        (else
         (cons (cons (car lst) (split (cddr lst))) (cons (cadr lst) (split (cddr lst)))))))

Next is the second base case, the answer to the (null? (cdr lst)) cond question. Again it should return a list of exactly two inner lists:

(list ??? ???)

The the first index should go in the first list, and then there's nothing to go in the second list.

(list (list (car lst)) '())

In the context of your code:

(define (split lst)
  (cond ((null? lst)
         (list '() '()))
        ((null? (cdr lst))
         (list (list (car lst)) '()))
        (else
         (cons (cons (car lst) (split (cddr lst))) (cons (cadr lst) (split (cddr lst)))))))

Now, what is the behavior of this function?

 > (split '(1 2 3 4 5 6))
 '((1 (3 (5 () ()) 6 () ()) 4 (5 () ()) 6 () ()) 2 (3 (5 () ()) 6 () ()) 4 (5 () ()) 6 () ())

Still not what you want. So what is the last case, recursive case, supposed to do?

Consider what you're "given" and what you need to "produce".

Given:

  • (car lst) the first element
  • (cadr lst) the second element
  • (split (cddr lst)) a list of exactly two inner lists

You should produce:

  • (list ??? ???)

Where the first ??? hole contains the first element and the first of the two inner lists, and the second ??? hole contains the second element and the second of the two inner lists.

This suggests code like this:

(list (cons (car lst)  (first (split (cddr lst))))
      (cons (cadr lst) (second (split (cddr lst)))))

Or, since car gets the first and cadr gets the second:

(list (cons (car lst)  (car (split (cddr lst))))
      (cons (cadr lst) (cadr (split (cddr lst)))))

In the context of your code:

(define (split lst)
  (cond ((null? lst)
         (list '() '()))
        ((null? (cdr lst))
         (list (list (car lst)) '()))
        (else
         (list (cons (car lst)  (car (split (cddr lst))))
               (cons (cadr lst) (cadr (split (cddr lst))))))))

Using it produces what you want:

> (split '(1 2 3 4 5 6))
'((1 3 5) (2 4 6))
> (split '(1 2 3 4 5 6 7))
'((1 3 5 7) (2 4 6))
> (split '("a" "little" "bit" "of" "that" "to" "spice" "things" "up"))
'(("a" "bit" "that" "spice" "up") ("little" "of" "to" "things"))

Now what was the difference between this and what you had before?

Your code before:

(cons (cons (car lst)  (split (cddr lst)))
      (cons (cadr lst) (split (cddr lst))))

The fixed version:

(list (cons (car lst)  (car (split (cddr lst))))
      (cons (cadr lst) (cadr (split (cddr lst)))))

The first difference is that your original version uses cons on the outside, while the fixed version uses list instead. This is because (list ??? ???) always returns a list of exactly two elements, while (cons ??? ???) can return a list of any size greater than 1, which has the first thing merged onto an existing second list. (list ??? ???) is what you want here because you specified that it should return a list of exactly two inner lists.

The second difference is in how you use the recursive call (split (cddr lst)) .

This has to do with how you interpreted the "given" part of the recursive case. You had assumed that the first call to split would give you the first "inner" list, and the second call to split would give you the second "inner" list. In fact it gives you a list of both of those both times. So for the first one you have to get the "first" or car of it, and for the second one you have get the "second" or cadr of it.

Looks like this might be what you're looking for:

(define (split lst)
  (define (loop lst do-odd odds evens)
    (if (null? lst)
    (list (reverse odds) (reverse evens))
    (loop (cdr lst) (not do-odd)
          (if do-odd (cons (car lst) odds) odds)
          (if (not do-odd) (cons (car lst) evens) evens))))
  (loop lst #t '() '()))

In use:

1 ]=> (split '(1 2 3 4 5 6))

;Value 2: ((1 3 5) (2 4 6))

1 ]=> (split '(1 2 3 4 5 6 7))

;Value 3: ((1 3 5 7) (2 4 6))

This uses the variable do-odd in the inner loop function (which is tail-recursive, by the way, so it is fast!) to figure out which list it should add the (car lst) to.

Downsides to this function: the call to reverse in the base case can be expensive if your lists are very long. This may or may not be a problem. Profiling your code will tell you if it's a bottleneck.

UPDATE : You can also use the function reverse! , which destructively modifies the array in question. I did some informal profiling, and it didn't seem to make that much of a difference speed-wise. You will have to test this under your specific circumstances.

Now, if this isn't intended to be performant, use whatever you want! :)

My shortest solution

(define (split l)
  (cond ((null? l) '(() ()))
        ((null? (cdr l)) (list (list (car l)) '()))
        (else (map cons (list (car l) (cadr l))
                        (split (cddr l))))))

Similar but wordier solution

Ensure that split always returns a list of two lists. Then you can define it quite compactly:

(define (split l)
  (cond ((null? l) '(() ()))
        ((null? (cdr l)) (list (list (car l)) '()))
        (else (double-cons (list (car l) (cadr l))
                           (split (cddr l))))))

with double-cons being:

(define (double-cons l lol)
  (list (cons (car l) (car lol)) 
        (cons (cadr l) (cadr lol))))

double-cons :

(double-cons '(a 1) '((b c) (2 3)))
; => '((a b c) (1 2 3))

Other double-cons definitions

This takes more lines but makes it easier to read:

(define (double-cons l lol)
  (let ((e1 (car l))
        (e2 (cadr l))
        (l1 (car lol))
        (l2 (cadr lol)))
    (list (cons e1 l1) (cons e2 l2))))

Or a double-cons which cons es even more elements and lists in parallel:

(define (parallel-cons l lol)
   (map cons l lol))
; it is `variadic` and conses as many elements with their lists
; as you want:
(parallel-cons '(1 a A '(a)) '((2 3) (b c d e) (B C) ((b) (c))))
; '((1 2 3) (a b c d e) (A B C) ('(a) (b) (c)))
; this combination of `map` and `cons` is used in the shortest solution above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM