我是Scheme(通过Racket)和(在较小程度上)函数式编程的新手,并且可以就通过变量与递归进行累积的利弊提出一些建议。出于本示例的目的,我正在尝试计算移动平均线。所以,对于一个列表'(1 2 3 4 5)
,3 个周期的移动平均线是'(1 2 2 3 4)
。这个想法是,周期之前的任何数字还不是计算的一部分,一旦我们达到集合中的周期长度,我们就开始根据所选周期对列表的子集进行平均。
所以,我的第一次尝试看起来像这样:
(define (avg lst)
(cond
[(null? lst) '()]
[(/ (apply + lst) (length lst))]))
(define (make-averager period)
(let ([prev '()])
(lambda (i)
(set! prev (cons i prev))
(cond
[(< (length prev) period) i]
[else (avg (take prev period))]))))
(map (make-averager 3) '(1 2 3 4 5))
> '(1 2 2 3 4)
这行得通。我喜欢使用地图。它似乎是可组合的并且可以重构。我可以看到将来有像这样的表亲:
(map (make-bollinger 5) '(1 2 3 4 5))
(map (make-std-deviation 2) '(1 2 3 4 5))
等等
但是,这不符合 Scheme 的精神(对吗?),因为我正在积累副作用。所以我把它改写成这样:
(define (moving-average l period)
(let loop ([l l] [acc '()])
(if (null? l)
l
(let* ([acc (cons (car l) acc)]
[next
(cond
[(< (length acc) period) (car acc)]
[else (avg (take acc period))])])
(cons next (loop (cdr l) acc))))))
(moving-average '(1 2 3 4 5) 3)
> '(1 2 2 3 4)
现在,乍一看,这个版本更难理解。所以我有几个问题:
有没有更优雅的方式来使用球拍的一些内置迭代结构(如
for/fold
)来表达递归版本?它甚至是书面的尾递归吗?有没有办法在不使用累加器变量的情况下编写第一个版本?
这种类型的问题是一个更大的模式的一部分,其中有公认的最佳实践,尤其是在 Scheme 中?