【问题标题】:Recursively adding to a list using Scheme使用 Scheme 递归添加到列表
【发布时间】:2019-11-16 23:14:40
【问题描述】:

我是函数式编程的新手,对整个编程来说也相对较新。所以我很难理解 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。当它尝试调用自身时,这给我一个“应用程序:不是过程”的错误再次。我已经解决了这个错误,但无法像我想要的那样远程打印任何东西。任何帮助表示赞赏。

【问题讨论】:

  • @RobertHarvey 是的,但这个问题中的问题实际上与附加到列表无关:算术括号错误。
  • shrug 对我来说似乎是一个非常相似的问题。
  • 所以基本上是(gen-list 2 4) ; ==> (append '() (append '() (append '() '()))) ==> ()。数字在哪里?您还试图调用算术运算的结果。又名Application: Not a procedure

标签: list recursion scheme lisp racket


【解决方案1】:

您不仅需要 (> start end) 基本案例,还需要一个 (= start end) 基本案例,在该基本案例中返回(列表开始)

【讨论】:

  • 因为(gen-list 1 5) => (5) 而不是(1 2 3 4 5),这将不起作用。确实是默认情况下是错误的。
【解决方案2】:

你有几个错误:

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

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

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

作为最后的评论:上面的过程已经存在于 Racket 中,你不需要重写它。在documentation 中了解range

【讨论】:

    【解决方案3】:

    这个问题的“显而易见”答案的一个问题是它并不能很好地工作。考虑一下:

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

    好吧,如果startend 少得多,堆栈上将会有大量的递归调用,因为这是一个适当的递归函数:对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 组合子。

    【讨论】:

      【解决方案4】:

      只添加尾调用递归版本

      (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 的风格,一本非常好的学习递归思维的书。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-25
        • 1970-01-01
        相关资源
        最近更新 更多