给定的代码在非尾部位置有应用程序,因此该函数不是迭代的,尽管它的名称。:)
试试这个:
(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 。边缘和边缘尾的某些退化输入会导致大量计算问题。
让我们举一个这种退化输入的例子:
(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和fringe-tail的计算时间只有几秒钟。这些测试在禁用调试的 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/tail快。尽管如此,这两者都比fringe好得多,因为它们避免了append。
话虽这么说:这个函数已经作为flatten函数内置在 Racket 中!因此,如果您不想重新发明轮子,不妨使用它。:)