【问题标题】:Splitting a list recursively in Scheme在 Scheme 中递归拆分列表
【发布时间】:2018-10-10 02:31:39
【问题描述】:

我想要做的是定义一个列表,例如 (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)

【问题讨论】:

  • lst(1 2 3 4 5 6 7) 时,(split lst) 应该返回什么?
  • 它应该返回 '((1 3 5 7) (2 4 6))
  • 这是否意味着它应该返回奇数,然后是偶数?还是别的什么?
  • 我认为算法的工作原理是在构建两个列表时它会交替出现。所以第一个索引将进入第一个列表,第二个列表中的 seocnd 索引,第一个列表中的第三个索引,等等。
  • 那么(list "a" "little" "bit" "of" "that" "to" "spice" "things" "up") 应该返回(list (list "a" "bit" "that" "spice" "up") (list "little" "of" "to" "things")) 吗?

标签: scheme racket


【解决方案1】:

让我们一步一步地修复你的代码:

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

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

(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 会给你第二个“内部”列表。事实上,它为您提供了这两次的 both 列表。所以对于第一个你必须得到它的“第一”或car,而对于第二个你必须得到它的“第二”或cadr

【讨论】:

  • 好吧,我理解您对此的想法,因为这与我自己尝试做的非常相似。在脚本的末尾。您正在使用未在函数中的任何位置定义的“第一”和“第二”。为什么?
  • 哦,在我使用的Scheme的实现中,firstcar的别名,secondcadr的别名。我使用它们是因为它们更具描述性,而且它们只是使代码更易于阅读。但我可以改变它们。
  • 哦不,没关系。我只是在读它。欣赏演练和解释!
  • 在实现Scheme I中使用,firstcar的别名,secondcadr的别名,但我一直使用carcadr(和 cddr),因为它们明确、简短、背后有丰富的历史,并且使代码更易于阅读——对我来说。: )
【解决方案2】:

看起来这可能是您正在寻找的:

(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!,它会破坏性地修改相关数组。我做了一些非正式的分析,它似乎并没有在速度方面产生太大的影响。您必须根据自己的具体情况对此进行测试。

现在,如果这不是为了提高性能,请使用任何你想要的东西! :)

【讨论】:

    【解决方案3】:

    我的最短解决方案

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

    或者conses 甚至更多元素和并行列表的双重缺点:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-22
      • 2019-11-16
      • 1970-01-01
      • 2018-07-20
      • 1970-01-01
      相关资源
      最近更新 更多