使用方案递归添加到列表中



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

(define (gen-list x y)
(if (> start end)
'()
(append '() (gen-list ((+ 1 x) y)) ) ))

如果我输入(gen-list 1 5),我希望结果是1 2 3 4 5。当它尝试再次调用自身时,这目前给了我"应用程序:不是过程"的错误。我已经解决了错误,但无法像我想要的那样远程打印任何内容。任何帮助,不胜感激。

您有几个错误:

  • 这些参数称为xy,但您将它们称为startend(我建议改用startend,它们使代码更容易理解。
  • 最后一行的括号多于所需。对于初学者来说,这非常重要,也是无休止的困惑来源。不要用()包围所有表达式,除非要调用过程。
  • 我们使用cons递归构建新列表,append用于连接现有列表。
  • 您实际上并没有使用start(递归中的当前元素)来构建新列表 - 您只是append空列表。
  • 列表是cons到另一个列表或空列表'()的元素。这就是为什么我们在基本情况下返回'()。例如,这是双元素列表的样子:(cons 1 (cons 2 '()))

综上所述,这是构建列表的正确方法:

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

最后评论:上述过程已经存在于 Racket 中,您无需重写它。阅读文档中的range

这个问题的"明显"答案的问题之一是它实际上效果不佳。 考虑一下:

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

好吧,如果start远小于end堆栈上将有大量的递归调用,因为这是一个正确的递归函数:对gen-list的递归调用是一个真正的调用,必须在调用cons(这是一个尾部调用)发生之前返回。

处理这个问题的方法是将看起来像(cons x (<recursive-call> ...))的模式变成看起来像(<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开头,然后向后构造列表。 所以,现在:

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

如我们所愿。

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

(define (gen-list low high)
(let gla ([i high] [result '()])
(if (< i low)
result
(gla (- i 1) (cons i result)))))

这与之前的答案完全相同:它只是将初始调用移动到顶部,并将其与gla的本地定义相结合。 这可能是做这样事情的惯用 Scheme 方式(尽管写比我更多的 Scheme 的人可能会有所不同:我真的是一个 CL 人,不可避免地品味不佳)。


故事应该到此结束,但我忍不住补充以下内容。在 comp.lang.lisp 的糟糕过去,人们过去常常问明显的家庭作业问题,由于没有业力系统,一种方法是给出解决问题的答案......同时荒谬地不透明。

因此,首先我们可以将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)

(define (gen-list low high)
((λ (gla)
(gla gla high '()))
(λ (cont i result)
(if (< i low)
result
(cont cont (- i 1) (cons i result))))))

这是一个很好的、纯粹的答案...没有学生会想出

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

只是添加尾部调用递归版本

(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风格。

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

最新更新