减少和减少会让您在序列中累积状态。 序列中的每个元素将修改累积状态,直到 到达序列的结尾。
在无限列表中调用reduce或reduce的含义是什么?
(def c (cycle [0]))
(reduce + c)
这会快速抛出OutOfMemoryError。顺便说一下,(reduce + (cycle [0]))
不会抛出OutOfMemoryError(至少在我等待的时候不会)。它永远不会回来。不知道为什么。
有没有办法以有意义的方式调用无限列表中的减少或减少?我在上面的例子中看到的问题是,最终列表的评估部分变得足够大以溢出堆。也许无限列表不是正确的范例。减少生成器,IO流或事件流会更有意义。在评估并用于修改状态后,不应保留该值。
答案 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