使用Scheme递归添加到列表

时间:2019-07-06 22:07:27

标签: list recursion scheme lisp racket

我对函数式编程是全新的,对整个编程而言还是相对较新的。所以我很迷茫,无法理解Scheme的语法。但这是一个非常简单的问题。我正在尝试创建一个函数来递归地填充并打印从数字x到y的列表。在递归和这对我来说是一门新语言之间,我感到很困惑。

 (define (gen-list x y)

  (if (> start end)

      '()

      (append '() (gen-list ((+ 1 x) y)) ) ))

如果我要输入(gen-list 1 5),我希望结果是1 2 3 45。这在尝试调用自身时会给我一个错误“应用程序:不是过程”再次。我已经解决了该错误,但是无法像我想要的那样远程打印任何内容。任何帮助表示赞赏。

4 个答案:

答案 0 :(得分:4)

您有几个错误:

  • 这些参数分别称为xy,但您将它们称为startend(我建议使用start和{ {1}}相反,它们使代码更易于理解。)
  • 您的括号比最后一行的要多。这是非常重要的,对于初学者来说,这是无尽的困惑。除非要调用过程,否则不要用end 包围所有表达式。
  • 我们使用()递归构建新列表,cons用于连接现有列表。
  • 您实际上并没有使用 append(即递归中的当前元素)来构建新列表-您只是start创建空列表。
  • 列表是元素append到另一个列表或空列表cons的元素。这就是为什么我们在基本情况下返回'()的原因。例如,这是一个包含两个元素的列表:'()

总而言之,这是构建列表的正确方法:

(cons 1 (cons 2 '()))

作为最后的评论:以上过程已经存在于Racket中,您不需要重写它。在documentation中了解(define (gen-list start end) (if (> start end) '() (cons start (gen-list (+ start 1) end))))

答案 1 :(得分:1)

这个问题的“明显”答案的问题之一是它实际上并不能很好地工作。考虑一下:

(define (gen-list start end)
  (if (> start end)
      '()
      (cons start
            (gen-list (+ start 1) end))))

好吧,如果startend小得多,则堆栈上将有大量的递归调用,因为这是一个适当的递归函数:对{{1 }}是真正的通话,必须在对gen-list的通话(这是尾部通话)发生之前返回。

处理此问题的方法是将看起来像cons的模式变成看起来像(cons x (<recursive-call> ...))的模式:您需要一个带有额外参数的函数,一个 accumulator 。这意味着以前是递归的调用现在是尾部调用,因此一切都很好:该过程现在是迭代的。

此问题是列表向后出现(您需要考虑这是为什么,但是经过一番思考,这很明显)。因此,您需要反转结果。幸运的是,反转列表也是一个反复的过程,所以没关系。

但是在这种情况下,您可以倒数!因此,使用本地定义的辅助功能(可以定义为顶级功能,但为什么要麻烦呢?)的简单思路如下:

(<tail-call> ... (cons x ...))

您可以看到这是倒数:对(define (gen-list low high) (define (gla i result) (if (< i low) result (gla (- i 1) (cons i result)))) (gla high '())) 的初始调用以gla开始,然后向后构造列表。所以,现在:

high

我们想要的。

这是Scheme中的一种常见模式,因此有一个特殊的构造:名为let。因此,我们可以更习惯地将以上内容重写为:

> (gen-list 1 3)
'(1 2 3)

这与先前的答案完全相同:它只是将初始调用移到顶部,并将其与(define (gen-list low high) (let gla ([i high] [result '()]) (if (< i low) result (gla (- i 1) (cons i result))))) 的本地定义组合在一起。这可能是做这种事情的惯用的Scheme方法(尽管编写Scheme比我更多的人可能会有所不同:我确实是一名CL人员,并且不可避免地具有不良品味)。


这是故事的结尾,但是我不能抗拒添加以下内容。在comp.lang.lisp过去的糟糕日子里,人们常常问一些明显的作业问题,并且由于没有业力系统,一种方法是给出一个解决问题的答案……而这是荒谬的。

因此,我们首先可以将gla变成一个函数,该函数将传递给连续函数以进行调用,而不是知道它必须自行调用:

gla

然后,我们当然可以将(define (gen-list low high) (let ([gla (λ (cont i result) (if (< i low) result (cont cont (- i 1) (cons i result))))]) (gla gla high '()))) 变成(let ([x y]) ...)

((λ (x) ...) y)

这是一个很好的,纯粹的答案……没有学生会想到的。

当然,另一种更为恶意的方法是显式使用Y组合器。

答案 2 :(得分:1)

只需添加尾调用递归版本

(define (gen-list start end (acc '()) #:step (step 1))
  (cond ((> start end) (reverse acc))
        (else (gen-list (+ start step) end (cons start acc)))))

我个人很喜欢cond,因为你们的所有条件然后又相形之下(或else)-这是The little Schemer的风格,是一本很好的学习递归思维的书。

答案 3 :(得分:-2)

您不仅需要(>开始结束)基本情况,还需要(=开始结束)基本情况,在此情况下您要返回(列表开始)