我有一个Clojure程序,它返回even
以下n
斐波那契数的惰性序列的总和:
(defn sum-of-even-fibonaccis-below-1 [n]
(defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))
(reduce + (take-while (partial >= n) (take-nth 3 (fib 0 1)))))
(time (dotimes [n 1000] (sum-of-even-fibonaccis-below-1 4000000))) ;; => "Elapsed time: 98.764msecs"
效率不高。但是如果我不减少序列并简单地返回值(0 2 8 34 144...)
的列表,那么它的工作速度可以提高20倍:
(defn sum-of-even-fibonaccis-below-2 [n]
(defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))
(take-while (partial >= n) (take-nth 3 (fib 0 1))))
(time (dotimes [n 1000] (sum-of-even-fibonaccis-below-2 4000000))) ;; => "Elapsed time: 5.145msecs"
为什么reduce
这个懒惰的Fibonacci序列如此昂贵,如何在不放弃惯用的Clojure的情况下加快速度?
答案 0 :(得分:7)
执行时间的差异是懒惰的结果。在sum-of-even-fibonaccis-below-2
中,您只生成一个懒惰的斐波那契数字序列,这些数字未实现(dotimes
仅调用sum-of-even-fibonaccis-below-2
来创建一个惰性序列,但不会计算其所有内容)。所以实际上你的第二个time
表达式不会返回一个值列表,而是一个懒惰的seq,只有当你要求它们时才会产生它的元素。
要强制实现延迟序列,如果您不需要将其保留为值,则可以使用dorun
;如果您想获得已实现的序列,则可以使用doall
(小心使用无限序列) )。
如果您使用sum-of-even-fibonaccis-below-2
中的dorun
来衡量第二个案例,那么您将获得与sum-of-even-fibonaccis-below-1
相当的时间。
我的机器的结果:
(time (dotimes [n 1000] (sum-of-even-fibonaccis-below-1 4000000))) ;; => "Elapsed time: 8.544193 msecs"
(time (dotimes [n 1000] (dorun (sum-of-even-fibonaccis-below-2 4000000)))) ;; => "Elapsed time: 8.012638 msecs"
我还注意到您在另一个fib
内使用defn
定义了defn
函数。您不应这样做,因为defn
将始终在命名空间的顶层定义函数。所以你的代码应该是这样的:
(defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))
(defn sum-of-even-fibonaccis-below-1 [n]
(reduce + (take-while (partial >= n) (take-nth 3 (fib 0 1)))))
(defn sum-of-even-fibonaccis-below-2 [n]
(take-while (partial >= n) (take-nth 3 (fib 0 1))))
如果您确实要定义本地范围的功能,可以查看letfn
。
答案 1 :(得分:-1)
<强>注释强>
你可以重构你的功能 - 并给他们更好的名字 - 因此:
(defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))
(defn even-fibonaccis-below [n]
(take-while (partial >= n) (take-nth 3 (fib 0 1))))
(defn sum-of-even-fibonaccis-below [n]
(reduce + (even-fibonaccis-below n)))
这更容易理解,因此可以回答。