Scheme中的Tail递归计数功能

时间:2012-03-28 19:36:18

标签: functional-programming scheme

该函数应该是尾递归的,并从1计数到指定的数字。我觉得我很亲密。这就是我所拥有的:

(define (countup l)
  (if (= 1 l) 
      (list l)
      (list
       (countup (- l 1))
       l
       )
    )
  )

但是,这显然会返回一个包含嵌套列表的列表。我试图使用append函数而不是第二个列表无济于事。有什么指导吗?

4 个答案:

答案 0 :(得分:1)

这是一个不正确的解决方案:

(define (countup n)
  (define (help i)
    (if (<= i n)
        (cons i (help (+ i 1)))
        '()))
  (help 1))

此解决方案:

  • 使用帮助函数
  • 将数字从1递增到n,将它们列入不断增长的列表

为什么这是错的?它不是真正的尾递归,因为它创建了一大串cons调用,无法立即进行评估。这会导致堆栈溢出,以获得足够大的n值。

这是解决此问题的更好方法:

(define (countup n)
  (define (help i nums)
    (if (> i 0)
        (help (- i 1)
              (cons i nums))
        nums)))
  (help n '()))

注意事项:

  • 这个解决方案更好,因为可以立即评估对cons的调用,因此该函数是尾递归优化(TCO)的候选者,在这种情况下,堆栈空间不会成为问题。
  • help向后追溯数字,从而避免使用append,这可能相当昂贵

答案 1 :(得分:1)

您应该使用辅助函数来实现此问题的尾递归解决方案(“循环”函数),并使用额外的参数来累积答案。像这样:

(define (countup n)
  (loop n '()))

(define (loop i acc)
  (if (zero? i)
      acc
      (loop (sub1 i) (cons i acc))))

或者,您可以使用命名let。无论哪种方式,解决方案都是尾递归的,并且参数用于累积值,请注意递归向后推进,从n开始并计数回0,即可每个值依次在列表的开头:

(define (countup n)
  (let loop ((i n)
             (acc '()))
    (if (zero? i)
        acc
        (loop (sub1 i) (cons i acc)))))

答案 2 :(得分:0)

这是代码的工作版本,它以正确的顺序返回列表(我将l替换为n):

(define (countup n)
 (if (= 1 n) 
     (list n)
     (append (countup (- n 1)) (list n))))

可悲的是,这段代码存在问题:它不是尾递归的。原因是对countup的递归调用不在尾部位置。它不在尾部位置,因为我正在追加(countup (- l 1))的结果,因此尾部调用为appendlist时为n = 1而不是countup {1}}。这意味着这段代码是一个普通的recusrive函数,但它是一个尾递归函数。

从维基百科中查看此link,以获得更好的示例,说明它不是尾部重新驱动的原因。

要使其尾递归,您需要有一个累加器来负责累积计数值。这样,您就可以将递归函数调用放在尾部位置。看看我给你的链接的差异。

如果您需要更多详细信息,请随时回复。

答案 3 :(得分:0)

假设这是一个学习练习,你想要这种行为:

(countup 5) => (list 1 2 3 4 5)

这是一个提示 - 在尾递归函数中,尾部位置的调用应该是自身的(除非是边缘情况)。

由于countup不包含数字列表,因此需要一个带有数字和列表的累加器函数,并返回一个列表。

这是一个模板:

;; countup : number -> (listof number)
(define (countup l)

  ;; countup-acc : number, (listof number) -> (listof number)
  (define (countup-acc c ls)
    (if ... 
        ...
        (countup-acc ... ...)))

  (countup-acc l null))

在对countup-acc的内部调用中,您需要更改在边缘情况下检查的参数,以使其更接近该边缘情况,并且您将需要更改另一个参数以使其更接近你想最终回归的是什么。