给定的代码在非尾部位置有应用程序,因此该函数不是迭代的,尽管它的名称。 :)
试试这个:
(define (fringe-tail tree)
(define (iter tree k)
(cond
[(null? tree)
(k '())]
[(not (pair? tree))
(k (list tree))]
[else
(iter (car tree)
(lambda (v1)
(iter (cdr tree)
(lambda (v2)
(k (append v1 v2))))))]))
(iter tree (lambda (a-fringe) a-fringe)))
但是,它仍然使用 append,这与它的第一个参数的长度一样昂贵。 fringe 和 fringe-tail 的某些退化输入会导致大量计算问题。
让我们举一个这种退化输入的例子:
(define (build-evil-struct n)
(if (= n 0)
(list 0)
(list (list (build-evil-struct (sub1 n)))
(build-evil-struct (sub1 n))
(list n))))
(define evil-struct (build-evil-struct 20))
当同时应用于 fringe 和 fringe-iter 时,您会看到非常糟糕的性能:我在自己的系统上观察到 fringe 几秒钟的计算时间 和 边缘尾巴。这些测试是在禁用调试的 DrRacket 下运行的。如果您启用调试,您的数字将有很大不同。
> (time (void (fringe evil-struct)))
cpu time: 2600 real time: 2602 gc time: 1212
> (time (void (fringe-tail evil-struct)))
cpu time: 4156 real time: 4155 gc time: 2740
对于这两种情况,append 的使用使它们容易受到某些退化输入的影响。如果我们编写 fringe 的累积版本,我们可以消除该成本,因为我们可以使用恒定时间 cons 操作:
(define (fringe/acc tree)
(define (iter tree acc)
(cond [(null? tree)
acc]
[(not (pair? tree))
(cons tree acc)]
[else
(iter (car tree) (iter (cdr tree) acc))]))
(iter tree '()))
我们看一下 fringe/acc 在这个结构上的表现:
> (time (void (fringe/acc evil-struct)))
cpu time: 272 real time: 274 gc time: 92
好多了!将这里的所有调用都变成尾调用是一件简单的事情。
(define (fringe/acc/tail tree)
(define (iter tree acc k)
(cond [(null? tree)
(k acc)]
[(not (pair? tree))
(k (cons tree acc))]
[else
(iter (cdr tree) acc
(lambda (v1)
(iter (car tree) v1 k)))]))
(iter tree '() (lambda (v) v)))
> (time (void (fringe/acc/tail evil-struct)))
cpu time: 488 real time: 488 gc time: 280
在这种特殊情况下,Racket 的堆栈实现比我们在延续中表示的具体堆栈快一点,因此 fringe/acc 比 fringe/acc /尾巴。尽管如此,这两者都明显优于 fringe,因为它们避免了 append。
话虽这么说:这个函数已经作为flatten 函数内置在Racket 中!因此,如果您不想重新发明轮子,不妨使用它。 :)