我是Clojure的新手,我认为到目前为止编写代码的方法与“Clojure方式”不一致。至少,我一直在编写使用大值导致StackOverflow错误的函数。我已经学会了使用recur,这是向前迈出的一大步。但是,如何使像下面这样的函数使用像2500000这样的值?
(defn fib [i]
(if (>= 2 i)
1
(+ (fib (dec i))
(fib (- i 2)))))
在我看来,这个函数是Fibonacci生成器的“普通”实现。我已经看到其他实现更加优化,但在他们的工作方面不那么明显。即当你阅读函数定义时,你不会去“哦,斐波那契”。
任何指针都将非常感谢!
答案 0 :(得分:6)
你需要有一个关于你的功能如何工作的心智模型。假设你自己执行你的函数,每次调用使用一张纸。第一个废料,你写(fib 250000)
,然后你看到“哦,我需要计算(fib 249999)
和(fib 249998)
并最终添加它们”,所以你要注意并开始两个新的废料。你不能扔掉第一个,因为当你完成其他计算时,它仍然有你要做的事情。你可以想象这个计算需要大量的资料。
另一种方法不是从顶部开始,而是从底部开始。你怎么用手工做?您将从第一个数字1,1,2,3,5,8 ...开始,然后始终添加最后两个,直到您完成i
次。你甚至可以扔掉除了最后两步之外的所有数字,所以你可以重复使用大部分废料。
(defn fib [i]
(loop [a 0
b 1
n 1]
(if (>= n i)
b
(recur b
(+ a b)
(inc n)))))
这也是一个相当明显的实现,但如何,而不是 what 。当你可以简单地写下定义并且它自动转换为有效的计算时,它似乎总是很优雅,但编程就是这种转换。如果某些东西自动转换,那么这个特殊问题已经得到解决(通常以更一般的方式)。
思考“我将如何在纸上逐步完成”通常会带来良好的实施。
答案 1 :(得分:5)
以“普通”方式实现的Fibonacci生成器,如序列的定义,将始终吹嘘你的筹码。对fib
的两次递归调用都不是尾递归的,这种定义无法优化。
不幸的是,如果你想编写一个适用于大数字的高效实现,你将不得不接受这样一个事实,即数学符号不会像我们希望的那样干净地转换为代码。
例如,可以在clojure.contrib.lazy-seqs中找到非递归实现。可以在Haskell wiki找到解决此问题的各种方法。理解函数式编程的基础知识应该不难理解。
答案 2 :(得分:-1)
;;from "The Pragmatic Programmers Programming Clojure"
(defn fib [] (map first (iterate (fn [[a b]][b (+ a b)])[0N 1N])))
(nth (fib) 2500000)