在Scheme中递归拆分列表

时间:2018-10-10 02:31:40

标签: scheme racket

我想做的是定义一个列表,例如(define lst'(1 2 3 4 5 6))然后调用(split lst)将返回'(((1 3 5)(2 4 6)) 。

一些例子:

  • lst'(1 2 3 4 5 6)时,它应返回'((1 3 5) (2 4 6))
  • lst'(1 2 3 4 5 6 7)时,它应返回'((1 3 5 7) (2 4 6))
  • lst'("a" "little" "bit" "of" "that" "to" "spice" "things" "up")时,它应返回'(("a" "bit" "that" "spice" "up") ("little" "of" "to" "things"))

在构建两个列表时,它应该交替显示。因此,第一个索引应放在第一个列表中,第二个索引应在第二个列表中,第三个索引应在第一个列表中,等等。

这是我当前的脚本。

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

当前,这是我分割列表'(1 2 3 4 5 6)时输出的内容

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

3 个答案:

答案 0 :(得分:4)

让我们逐步修正代码:

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

我注意到的第一件事是在else的最后一种情况下缺少cond。康德应该看起来像:

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

插入else后,您的代码如下:

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

接下来的事情是第一个基本情况,即(null? lst) cond问题的答案。在空列表上,它应该返回什么?

似乎无论列表有多长,它都应该始终返回恰好两个内部列表的列表。因此,当lst为空时,逻辑答案将为(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)))))))

接下来是第二种基本情况,即(null? (cdr lst)) cond问题的答案。同样,它应该返回一个恰好两个内部列表的列表:

(list ??? ???)

第一个索引应该在第一个列表中,然后第二个列表中没有任何内容。

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

在您的代码上下文中:

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

现在,此功能的行为是什么?

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

仍然不是您想要的。那么最后一种情况,递归情况应该做什么?

考虑您“被给予”的东西以及您需要“产生”的东西。

给出:

  • (car lst)第一个元素
  • (cadr lst)第二个元素
  • (split (cddr lst))正好是两个内部列表的列表

您应该制作:

  • (list ??? ???)

第一个???孔包含两个内部列表中的第一个元素和第一个,而第二个???孔包含两个内部列表中的第二个元素和第二个。

这建议这样的代码:

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

或者,由于car获得第一个,cadr获得第二个:

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

在您的代码上下文中:

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

使用它可以产生您想要的东西:

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

现在和以前相比有什么区别?

您之前的代码:

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

固定版本:

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

第一个区别是您的原始版本在外部使用cons,而固定版本则使用list。这是因为(list ??? ???)总是返回正好两个元素的列表,而(cons ??? ???)可以返回任何大小大于1的列表,该列表将第一件事合并到现有的第二列表中。 (list ??? ???)是您想要的,因为您指定它应返回正好是两个内部列表的列表。

第二个区别是您如何使用递归调用(split (cddr lst))

这与您如何解释递归案例的“给定”部分有关。您假设第一次调用split会给您第一个“内部”列表,而第二次调用split会给您第二个“内部”列表。实际上,它为您同时提供了这两个时间的列表。因此,对于第一个,您必须获得它的“第一”或car,对于第二个,您必须获得它的“第二”或cadr

答案 1 :(得分:0)

您可能正在寻找这样的东西

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

使用中:

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

这将使用内部循环函数中的变量do-odd(顺便说一下,它是尾递归的,所以它很快!)来找出应将(car lst)添加到哪个列表。

此功能的缺点:如果列表很长,在基本情况下对reverse的调用可能会很昂贵。这可能是问题,也可能不是问题。对代码进行性能分析会告诉您是否存在瓶颈。

更新:您还可以使用函数reverse!,该函数具有破坏性地修改了相关数组。我做了一些非正式的分析,但似乎在速度方面并没有太大的不同。您将必须根据自己的具体情况对此进行测试。

现在,如果不打算这样做,请使用所需的任何内容! :)

答案 2 :(得分:0)

我最短的解决方案

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

类似但冗长的解决方案

确保split始终返回两个列表的列表。 然后您可以非常紧凑地定义它:

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

其中double-cons为:

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

其他double-cons个定义

这会占用更多行,但更易于阅读:

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

或者是cons可以并行包含更多元素和列表的double-cons:

(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.