Clojure:减少,减少和无限列表

时间:2011-03-08 02:00:55

标签: clojure

减少和减少会让您在序列中累积状态。 序列中的每个元素将修改累积状态,直到 到达序列的结尾。

在无限列表中调用reduce或reduce的含义是什么?

(def c (cycle [0]))
(reduce + c)

这会快速抛出OutOfMemoryError。顺便说一下,(reduce + (cycle [0]))不会抛出OutOfMemoryError(至少在我等待的时候不会)。它永远不会回来。不知道为什么。

有没有办法以有意义的方式调用无限列表中的减少或减少?我在上面的例子中看到的问题是,最终列表的评估部分变得足够大以溢出堆。也许无限列表不是正确的范例。减少生成器,IO流或事件流会更有意义。在评估并用于修改状态后,不应保留该值。

4 个答案:

答案 0 :(得分:16)

它将永远不会返回,因为reduce接受一个序列和一个函数并应用该函数,直到输入序列为空,然后才知道它具有最终值。

减少真正无限的seq并不会有很大意义,除非它产生副作用,如记录其进展。

在第一个示例中,您首先要创建一个引用无限序列的var。

(def c (cycle [0]))

然后你传递var c的内容以减少开始读取元素以更新其状态。

(reduce + c)

这些元素不能被垃圾收集,因为var c拥有对第一个元素的引用,而第一个元素依次保存对第二个元素的引用,依此类推。最终,它读取的数量与堆中的空间一样多,然后是OOM。

为了避免在第二个例子中吹掉堆,你没有保留对已经使用的数据的引用,因此循环返回的seq上的项目GCd与生成时一样快,累积结果继续得到大。最终会溢出一个长的崩溃(clojure 1.3)或将自己提升为BigInteger并增长到所有堆的大小(clojure 1.2)

(reduce + (cycle [0]))

答案 1 :(得分:12)

亚瑟的答案很好,但看起来他没有解决你关于reductions的第二个问题。 reductions返回reduce 返回的中间阶段的延迟序列(如果仅列出N个元素长的话)。因此,在无限列表中调用reductions是完全合理的:

user=> (take 10 (reductions + (range)))
(0 1 3 6 10 15 21 28 36 45)

答案 2 :(得分:2)

如果您想继续从IO流等列表中获取项目并在运行之间保持状态,则不能使用 doseq (不使用 def )。相反,一个好的方法是使用 loop / recur ,这样可以避免占用过多的堆栈空间并让你保持状态,在你的情况下:

 (loop [c (cycle [0])]
   (if (evaluate-some-condition (first c))
     (do-something-with (first c) (recur (rest c)))
     nil))

当然,与你的情况相比,这里有一个条件检查,以确保我们不会无限循环。

答案 3 :(得分:0)

正如其他人所指出的那样,直接在无限序列上运行reduce是没有意义的,因为reduce是非惰性的并且需要消耗完整的序列。

作为这种情况的替代方案,这里有一个有用的功能,它只减少序列中的前n个项目,使用recur以合理的效率实现:

 (defn counted-reduce 
  ([n f s] 
    (counted-reduce (dec n) f (first s) (rest s) ))
  ([n f initial s]
    (if (<= n 0)
      initial
      (recur (dec n) f (f initial (first s)) (rest s)))))

(counted-reduce 10000000 + (range))
=> 49999995000000