Clojure seq 返回函数与 seq 的直接 'def'
Clojure seq returning function vs direct 'def' of seq
Clojure 新手问题。下面两种方式implement/represent斐波那契数列的优缺点是什么? (特别是,有没有什么可以完全排除一个或另一个坏主意。)
(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) 这两种定义无限序列的方法的优缺点是什么? (我知道这是一个有点特殊的情况,因为这个特定的序列不需要提供任何参数 - 不像 'n' 的倍数的无限序列,我认为这需要第一种方法,以便指定 'n' 的值。)
b) 是否有任何总体理由更喜欢这些实现中的一个而不是另一个? (内存消耗,作为参数时的适用性等)
如果多次查找其元素,fib-2
有利于时间性能,因为在惰性序列中,它们只需要计算一次。
由于全局绑定,seq 不太可能成为垃圾收集器,因此如果您的程序将通过一百万次斐波那契来进行一次计算,如果它不需要保持 seqs head 则更是如此,在本地上下文中调用 fib-1
有利于 space 性能。
这取决于您的使用情况,以及不必多次重新计算 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]
以避免 ArithmeticException integer overflow
在获取大量 fib 数字序列时。
有趣的是,改为使用 fib-2 会产生相同的 OOM 错误,但非头部保持版本会中断:
(let [[t d] (split-with #(< % 10) (take 1e6 fib-2))]
[(count t) (count d)])
=> [7 270036]
后一个数字应该是999993。
这两种情况的OOM原因如Clojure Programming所述:
Since the last reference of t occurs before the processing of d, no
reference to the head of the range sequence is kept, and no memory
issues arise.
Clojure 新手问题。下面两种方式implement/represent斐波那契数列的优缺点是什么? (特别是,有没有什么可以完全排除一个或另一个坏主意。)
(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) 这两种定义无限序列的方法的优缺点是什么? (我知道这是一个有点特殊的情况,因为这个特定的序列不需要提供任何参数 - 不像 'n' 的倍数的无限序列,我认为这需要第一种方法,以便指定 'n' 的值。)
b) 是否有任何总体理由更喜欢这些实现中的一个而不是另一个? (内存消耗,作为参数时的适用性等)
fib-2
有利于时间性能,因为在惰性序列中,它们只需要计算一次。
由于全局绑定,seq 不太可能成为垃圾收集器,因此如果您的程序将通过一百万次斐波那契来进行一次计算,如果它不需要保持 seqs head 则更是如此,在本地上下文中调用 fib-1
有利于 space 性能。
这取决于您的使用情况,以及不必多次重新计算 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]
以避免 ArithmeticException integer overflow
在获取大量 fib 数字序列时。
有趣的是,改为使用 fib-2 会产生相同的 OOM 错误,但非头部保持版本会中断:
(let [[t d] (split-with #(< % 10) (take 1e6 fib-2))]
[(count t) (count d)])
=> [7 270036]
后一个数字应该是999993。
这两种情况的OOM原因如Clojure Programming所述:
Since the last reference of t occurs before the processing of d, no reference to the head of the range sequence is kept, and no memory issues arise.