Clojure seq返回功能与直接' def' seq

时间:2015-05-20 09:49:06

标签: clojure

新手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)是否有任何总体理由更喜欢这些实现中的一种? (内存消耗,用作参数时的适用性等)

2 个答案:

答案 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的处理之前,没有   保留了对范围序列的头部的引用,并且没有存储器   问题出现了。