以下是我在Clojure和Python中为懒惰无限序列的Fibonacci数找到的最佳实现:
Clojure的:
(def fib-seq (lazy-cat [0 1]
(map + fib-seq (rest fib-seq))))
样本用法:
(take 5 fib-seq)
的Python:
def fib():
a = b = 1
while True:
yield a
a,b = b,a+b
样本用法:
for i in fib():
if i > 100:
break
else:
print i
显然,Python代码更加直观。
我的问题是:在Clojure中有更好的(更直观和简单的)实现吗?
我正在打开一个跟进问题 Clojure Prime Numbers
答案 0 :(得分:36)
我同意帕维尔的说法,直觉是主观的。因为我(慢慢地)开始讨厌Haskell,我可以告诉Clojure代码做了什么,即使我在生活中从未写过一行Clojure。因此我认为Clojure系列相当直观,因为我之前已经看过它,并且我正在适应更具功能性的思维模式。
我们应该考虑数学定义吗?
{ 0 if x = 0 }
F(x) = { 1 if x = 1 }
{ F(x - 1) + F(x - 2) if x > 1 }
这不太理想,格式明智 - 排列的三个括号应该是一个巨大的支架 - 但是谁在数?对于具有数学背景的大多数人来说,这是斐波那契序列的一个非常明确的定义。让我们看一下Haskell中的相同内容,因为我比Clojure更了解它:
fib 0 = 0
fib 1 = 1
fib n = fibs (n - 1) + fibs (n - 2)
这是一个函数fib
,它返回第n个Fibonacci数。不完全是我们在Python或Clojure中所拥有的,所以让我们解决这个问题:
fibs = map fib [0..]
这使fibs
成为Fibonacci数的无限列表。 fibs !! 1
为1,fibs !! 2
为1,fibs !! 10
为55,依此类推。但是,即使在依赖于大量优化的递归(如Haskell)的语言中,这可能效率也很低。让我们看看Haskell中的Clojure定义:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
前几个字符很简单:0 : 1 :
创建一个包含元素0和1的列表,然后再创建一些字符。但其余的都是什么呢?好吧,fibs
是我们已经获得的列表,tail fibs
到目前为止调用我们列表中的tail
函数,它返回从第2个元素开始的列表(类似于Python说fibs[1:]
)。我们采用这两个列表 - fibs
和tail fibs
- 我们将它们与+
函数(运算符)一起压缩 - 也就是说,我们添加每个列表的匹配元素。我们来看看:
fibs = 0 : 1 : ...
tail fibs = 1 : ...
zip result = 1 : ...
所以我们的下一个元素是1!但是我们将它添加到我们的fibs
列表中,看看我们得到了什么:
fibs = 0 : 1 : 1 : ...
tail fibs = 1 : 1 : ...
zip result = 1 : 2 : ...
我们这里有一个递归列表定义。当我们使用fibs
位向zipWith (+) fibs (tail fibs)
末尾添加更多元素时,我们可以在添加元素时使用更多元素。请注意,默认情况下Haskell是惰性的,所以只要创建一个这样的无限列表就不会崩溃(只是不要尝试打印它)。
因此,虽然理论上这可能与我们之前的数学定义相同,但它会将结果保存在我们的fibs
列表中(类似于自动记忆),我们很少遇到可能在天真中遇到的问题解。为了完整性,让我们根据新的fib
列表定义我们的fibs
函数:
fib n = fibs !! n
如果我没有失去你,那很好,因为这意味着你了解Clojure代码。看:
(def fib-seq (lazy-cat [0 1]
(map + fib-seq (rest fib-seq))))
我们制作一个列表fib-seq
。它以两个元素[0 1]
开头,就像我们的Haskell示例一样。我们使用(map + fib-seq (rest fib-seq))
对这两个初始元素进行延迟连接 - 假设rest
与Haskell中的tail
做同样的事情,我们只是将我们的列表与自身组合在一个较低的偏移量,然后将这两个列表与+
运算符/函数组合。
经过几次这样的工作,并探索其他一些例子,这种产生斐波纳契系列的方法至少变得半直观。它至少足够直观,让我用一种我不知道的语言来发现它。
答案 1 :(得分:14)
我喜欢:
(def fibs
(map first
(iterate
(fn [[ a, b ]]
[ b, (+ a b) ])
[0, 1])))
这似乎与python / generator版本有一些相似之处。
答案 2 :(得分:12)
如果您不知道任何命令式语言,这对您来说是否直观?
a = a + 5
WTF? a
明确与a + 5
不同。
如果a = a + 5
,是a + 5 = a
?
为什么这不起作用?
if (a = 5) { // should be == in most programming languages
// do something
}
除非你在其他地方见过并理解其目的,否则有很多事情都不清楚。很长一段时间我都不知道yield
关键字,实际上我无法弄清楚它是做什么的。
您认为必要的方法更易读,因为您已经习惯了。
答案 3 :(得分:6)
Clojure代码对我来说很直观(因为我知道Clojure)。如果你想要的东西看起来更像你熟悉的东西,你可以尝试这个,一个高效且过于冗长的递归版本:
(use 'clojure.contrib.def) ; SO's syntax-highlighting still sucks
(defn-memo fib [n]
(cond (= n 0) 0
(= n 1) 1
:else (+ (fib (- n 1))
(fib (- n 2)))))
(def natural-numbers (iterate inc 0))
(def all-fibs
(for [n natural-numbers]
(fib n)))
但对于不知道递归或记忆是什么的人来说,这也不是直观的。对于大多数程序员来说,“无限懒惰序列”的想法可能并不直观。我猜不出你脑子里有什么,所以我不知道你看起来更直观的Clojure功能,除了“看起来更像Python”。
对于不懂编程的人来说,所有这些东西都会看起来像胡言乱语。什么是循环?什么是功能?这yield
是什么东西?这就是我们所有人开始的地方。直觉是你到目前为止所学到的功能。非直观的代码是您不熟悉的代码。从“我知道这个”到“它本质上更直观”的推断是错误的。
答案 4 :(得分:5)
wiki对Clojure中各种Fibonacci实现进行了深入处理,如果您还没有看到它,可能会对您感兴趣。
答案 5 :(得分:2)
您应始终使用符合问题 *
的语言。你的Python示例比Clojure低一级 - 对于初学者来说更容易理解,但是对于那些知道更高级别概念的人来说,编写和解析会更加繁琐。
*
顺便说一句,这也意味着拥有一种可以适应的语言总是很好。
答案 6 :(得分:2)
这是一个解决方案。
(defn fib-seq [a b]
(cons (+ a b) (lazy-seq (fib-seq (+ a b) a))))
(def fibs (concat [1 1] (fib-seq 1 1)))
user=> (take 10 fibs)
(1 1 2 3 5 8 13 21 34 55)
答案 7 :(得分:-1)
想想你如何用clojure中的recur写懒惰猫。
答案 8 :(得分:-5)
(take 5 fibs)
似乎尽可能直观。我的意思是,这正是你正在做的事情。你甚至不需要了解任何关于语言的知识,甚至不知道是什么语言,以便知道应该发生什么。