新手Clojure问题。以下两种实现/表示Fibonacci序列的方法有哪些优缺点? (特别是,有什么可以完全排除一个或另一个是一个坏主意。)
(ns clxp.fib
(:gen-class))
; On the one hand, it seems more natural in code to have a function that
; returns 'a' Fibonacci sequence.
(defn fib-1
"Returns an infinite sequence of Fibonnaci numbers."
[]
(map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
; > (take 10 (fib-1))
; (0 1 1 2 3 5 8 13 21 34)
; On the other hand, it seems more mathematically natural to define 'the'
; Fibonacci sequence once, and just refer to it.
(def fib-2
"The infinite sequence of Fibonnaci numbers."
(map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
; > (take 10 fib-2)
; (0 1 1 2 3 5 8 13 21 34)
a)这两种定义无限序列的方法有哪些优点和缺点? (我知道这是一个有点特殊的情况,因为这个特定的序列不需要提供args - 不像,比如,无数序列的多个'&n;',我认为这需要第一种方法,以指定' n'的值。)
b)是否有任何总体理由更喜欢这些实现中的一种? (内存消耗,用作参数时的适用性等)
答案 0 :(得分:3)
fib-2
如果要多次查找其元素,最好支持时间性能,因为在懒惰的seq中,它们只需要计算一次。
由于全局绑定,seq不太可能被垃圾收集,所以如果你的程序将逐步通过一百万个fibonaccis进行一次计算,如果它不需要保持seqs头部则更是如此,在本地环境中调用fib-1
更有利于空间性能。
答案 1 :(得分:2)
这取决于您的使用情况,以及不必多次重新计算fib seq的重要性。但是,从我下面的实验中,我在使用长序列时遇到了def的问题。
如果你要引用很多元素,那么你需要注意头部保留,正如Leon所说。
这可以说明如下(这些扩展了Clojure编程中的几个例子):
(let [[t d] (split-with #(< % 10) (take 1e6 (fib-1)))]
[(count d) (count t)])
=> OutOfMemoryError Java heap space
(let [[t d] (split-with #(< % 10) (take 1e6 (fib-1)))]
[(count t) (count d)])
=> [7 999993]
注意,我必须更改您的实现以使用初始向量[0 1N]
以避免在获取大量fib数时ArithmeticException integer overflow
。
有趣的是,更改为使用fib-2而不是产生相同的OOM错误来保持头部情况,但非头部控制版本中断:
(let [[t d] (split-with #(< % 10) (take 1e6 fib-2))]
[(count t) (count d)])
=> [7 270036]
后一个数字应为999993。
两种情况下OOM的原因如Clojure Programming中所述:
由于t的最后一次引用发生在d的处理之前,没有 保留了对范围序列的头部的引用,并且没有存储器 问题出现了。