Clojure和Python中的懒惰无限序列

时间:2009-10-19 07:47:42

标签: python clojure

以下是我在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

9 个答案:

答案 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:])。我们采用这两个列表 - fibstail 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)

似乎尽可能直观。我的意思是,这正是你正在做的事情。你甚至不需要了解任何关于语言的知识,甚至不知道是什么语言,以便知道应该发生什么。