12

我注意到 Clojure 中的惰性序列似乎在内部表示为链表(或者至少它们被视为只能顺序访问元素的序列)。即使在缓存到内存之后,惰性序列上的访问时间nth也是 O(n),而不是像向量那样的恒定时间。

;; ...created my-lazy-seq here and used the first 50,000 items

(time (nth my-lazy-seq 10000))
"Elapsed time: 1.081325 msecs"

(time (nth my-lazy-seq 20000))
"Elapsed time: 2.554563 msecs"

如何在 Clojure 中获得恒定时间查找或增量创建惰性向量?

想象一下,在惰性向量的生成过程中,每个元素都是它之前所有元素的函数,因此遍历列表所花费的时间成为一个重要因素。

相关问题只出现了这个不完整的 Java 片段: Designing alazy vector: question with const

4

1 回答 1

20

是的,Clojure 中的序列被描述为具有三个操作(first、next 和 cons)的“逻辑列表” 。

序列本质上是迭代器的 Clojure 版本(尽管 clojure.org 坚持认为序列不是迭代器,因为它们不保持内部状态),并且只能以线性前端到端的方式在支持集合中移动。

惰性向量不存在,至少在 Clojure 中不存在。

如果您希望在一系列索引上进行恒定时间查找,而不计算不需要的中间元素,则可以使用动态计算结果的函数。结合记忆化(或将结果缓存在您自己的 arg-to-result 哈希中),您可以获得与我假设您希望从惰性向量中获得的几乎相同的效果。

这显然只有在算法可以比通过所有前面的 f(0)...f(n-1) 更直接地计算 f(n) 时才有效。如果没有这样的算法,当每个元素的结果都依赖于每个前一个元素的结果时,无论如何你都不能比序列迭代器做得更好。

编辑

顺便说一句,如果您想要的只是结果是一个向量,以便之后可以快速查找,并且您不介意第一次按顺序创建元素,那很简单。

这是使用向量的斐波那契实现:

(defn vector-fib [v]
  (let [a (v (- (count v) 2)) ; next-to-last element
        b (peek v)]   ; last element
    (conj v (+ a b))))

(def fib (iterate vector-fib [1 1]))

(first (drop 10 fib))
  => [1 1 2 3 5 8 13 21 34 55 89 144]

这里我们使用惰性序列来推迟函数调用直到被请求(iterate返回一个惰性序列),但是结果被收集并返回在一个向量中。

向量根据需要增长,我们只将元素添加到最后一个请求的元素,并且一旦计算出来,它就是一个常数时间查找。

你有没有这样的想法?

于 2010-06-21T10:40:57.003 回答