当你需要处理seq两次时,如何在不保留头部的情况下实现懒惰的seq?

时间:2016-12-24 03:09:45

标签: clojure

当一个函数被赋予一个大的延迟seq时,避免保留头部是有益的,这样如果完全实现的序列不适合内存,你仍然可以处理它。例如,这很好用:

(count (take 10000000 (range)))
(reduce + (take 10000000 (range)))

但这会产生内存不足错误:

(defn recount [coll n]
  [(count (take n coll))
   (reduce + (take n coll))])
(recount (range) 10000000)

因为当计数实现了懒惰的序列时,coll的绑定会保留序列的头部。

我能想出的最接近的东西是一个强制重新评估seq而不是绑定的宏:

(defmacro recount4 [coll n]
  `[(count (take ~n ~coll))
    (reduce + (take ~n ~coll))])
(recount4 (range) 10000000)

这似乎没有广泛适用。

我看了this blog,但由于原子和可变状态的使用,解决方案不太令人满意。

2 个答案:

答案 0 :(得分:5)

您可能希望查看eduction - 它会创建一个延迟的,非缓存的顺序集合,并将通过缩减重新评估每次使用的完整集合。

答案 1 :(得分:0)

你想要的是eduction

它允许对集合进行迭代或缩减。它在大多数情况下都像一个集合,但没有实现潜在的懒惰seq。

但是,

count不适用于教育,因此我们必须重写count作为缩减。

(defn recount5 [coll n]
  (let [s (eduction (take n) coll)]
    [(reduce (fn [r x] (inc r)) 0 s)
     (reduce + s)]))
(recount5 (range) 10000000)