以下Clojure计划有什么问题? Fibonacci的Memoized版本

时间:2015-05-27 19:41:48

标签: clojure

(def fibVal {1 1 2 1})

(defn fibonacci [x]               
  (if (false? (get fibVal x false)) 
    (do
      (println (str "Evaluating " x))
      (def fibVal (assoc fibVal x (+ (fibonacci(- x 1)) (fibonacci(- x 2)))))
      (println (str x " Evaluated to " (fibVal x)))
      (fibVal x)                                
    )
    (get fibVal x)
  )
)

(斐波那契5)的输出 评估5
评估4
评估3
3评估为2
4评估为3
评估3
3评估为2
5评估为5
5

3被评估两次,而在memoized版本中,它只应评估一次。

2 个答案:

答案 0 :(得分:4)

在除顶级表单之外的任何内容中使用def都不是线程安全的,并且不能保证在您使用它时起作用。为了存储像这样改变的状态,你很可能想要使用一个可变状态选项,如atoms,refs或agents。

在这种情况下,原子将是一个不错的选择。

答案 1 :(得分:2)

您的程序存在以下形式:

(def fibVal (assoc fibVal
                x (+ (fibonacci (- x 1)) 
                     (fibonacci (- x 2)))))

在递归调用写入新版本之前,将在第一行中使用的fibVal计算为其当前值。当fibVal表达式最终计算时,它们对def var所做的任何操作都将被遗忘,因为它们在被调用之前变为fibVal,其返回值的总和与x相关联{1}}。

def旨在用于顶级声明,而不是用于在递归过程中改变全局变量。

此外,您的递归实现不是迭代的,因此它会将堆栈吹得足够高。

以下是带有memoization的有状态迭代实现的示例:

(def fib-cache (atom [0 1]))

(defn- calc-nth-fib
  [fibs n]
  (reduce (fn [fibs n]
            (assoc fibs n
                   (apply + (take 2 (rseq fibs)))))
          fibs
          (range (count fibs) (inc n))))

(defn fibonacci [x]
  (or (get @fib-cache x)
      (-> fib-cache
          (swap! calc-nth-fib x)
          (nth x))))

请注意,此示例并不代表在Clojure中找到第n个Fibonacci的惯用方法,因为这需要在设计了延迟序列的单个线程上生成直到第n个数字的整个序列。它们隐式提供缓存,并针对所需的用例进行了优化。

对于惯用的Fibonacci实现,请参考众多lazy Fibonacci implementations中的一个,如有必要learn about lazy sequences.