我有一些代码可以找到最大高度并用相关名称替换它。高度和名称有单独的列表,每个列表长度相同且非空。
我可以使用结构递归解决这个问题,但必须将其改为累积,我不确定如何做到这一点。我见过的所有例子让我很困惑。是否有人能够使用累积递归将代码转换为一个?
(define (tallest names heights)
(cond
[(empty? names) heights]
[(> (first heights) (first (rest heights)))
(cons (first names) (tallest (rest (rest names)) (rest (rest heights))))]
[else (tallest (rest names) (rest heights))]))
答案 0 :(得分:2)
首先,您提供的tallest
功能实际上并不起作用(调用(tallest '(Bernie Raj Amy) (list 1.5 1.6 1.7))
失败并出现合同错误),但我看到了您所获得的内容。结构递归和累积递归之间的区别是什么?
好吧,结构递归通过构建结构作为返回值来工作,其中该结构中的一个值是对同一函数的递归调用的结果。例如,采用阶乘的递归计算。您可以这样定义:
(define (factorial n)
(if (zero? n) 1
(* n (factorial (sub1 n)))))
可视化此程序如何为4
的输入执行。每次通话都会留下一个洞#34;在乘法表达式中,由递归子句的结果填充。以下是使用_
代表其中一个"洞"的可视化形象。
(* 4 _)
(* 3 _)
(* 2 _)
(* 1 _)
1
请注意,只有在达到最终案例后,才能完成大部分工作。大部分工作必须在返回时从堆栈中弹出调用的过程中完成,因为每次调用都取决于对其子结果执行一些额外的操作。
累积递归有何不同?好吧,在累积递归中,我们对名为 accumulator 的函数使用了一个额外的参数。重写上面的阶乘函数以使用累加器会使它看起来像这样:
(define (factorial n acc)
(if (zero? n) acc
(factorial (sub1 n) (* acc n))))
现在,如果我们想要找到4的阶乘,我们必须调用(factorial 4 1)
,为累加器提供一个起始值(我将在稍后解决如何避免这种情况) 。如果你现在考虑调用堆栈,它会看起来很不一样。
注意没有"漏洞"填写 - factorial
函数的结果是累加器或直接调用自身。这称为尾调用,对factorial
的递归调用称为尾部位置。
由于一些原因,这有助于提供帮助。首先,使用累积递归更容易表达某些函数,但factorial
可能不是其中之一。但更重要的是,Scheme要求实现提供正确的尾部调用(有时也称为"尾部调用优化"),这意味着当尾部调用时调用堆栈不会增长是。
关于尾调用的工作方式以及它们有用的原因,有很多现有的信息,所以我不会在这里重复一遍。重要的是要理解 accumulative 递归涉及 accumulator 参数,这通常会导致结果函数通过尾调用实现。
但是我们如何处理额外的参数呢?好吧,我们实际上可以做一个帮助"将执行累积递归的函数,但我们将提供一个自动填充基本情况的函数。
(define (factorial n)
(define (factorial-helper n acc)
(if (zero? n) acc
(factorial-helper (sub1 n) (* acc n))))
(factorial-helper n 1))
这种成语很常见,因为Racket提供了一个名为let
"形式,简化了上述功能:
(define (factorial n)
(let helper ([n n] [acc 1])
(if (zero? n) acc
(helper (sub1 n) (* acc n)))))
但对于同样的想法,这只是一些语法糖。
好的,所以:这些问题如何适用于您的问题?实际上,使用累积递归可以很容易地实现您的问题。以下是您如何构建算法的细分:
empty
。这将构成您的基本情况"。将这些全部放在一起,这是一个简单的实现:
(define (tallest-helper names heights current-tallest)
(cond
[(empty? names)
(car current-tallest)]
[(> (first heights) (cdr current-tallest))
(tallest-helper (rest names) (rest heights)
(cons (first names) (first heights)))]
[else
(tallest-helper (rest names) (rest heights)
current-tallest)]))
这可以通过多种方式进一步改进 - 将其包装在一个提供累加器起始值的函数中,使用名为let
,删除一些重复等等 - 但我和#39把它作为锻炼留给你。
请记住:累加器实际上是你的"工作总和"。它是你的"跑步总数"。理解这一点,事情应该有意义。