2011-03-08 144 views
12

减少和减少让你积累一个序列的状态。 序列中的每个元素将修改累计状态,直到达到序列末尾的 。Clojure:减少,减少和无限列表

对无限列表调用reduce或reduce会有什么影响?

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

这将很快抛出OutOfMemoryError。顺便说一句,(reduce + (cycle [0]))不会抛出一个OutOfMemoryError(至少不是我等待的时间)。它永远不会返回。不知道为什么。

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

回答

15

它永远不会返回,因为减少了一个序列和一个函数并应用该函数,直到输入序列为空,才知道它具有最终值。

减少一个真正无限的seq不会产生很大的意义,除非它产生一种副作用,如记录进度。

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

(def c (cycle [0])) 

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

(reduce + c) 

这些元素不能被垃圾收集因为VARÇ保持到第一他们的,这反过来又保持对所述第二等的基准的基准。最终它会读取堆中空间,然后是OOM。

为了避免在第二个示例中吹出堆,您没有保留对已经使用过的数据的引用,这样循环返回的seq上的项目的GCd与生成的GCd一样快,并且累积结果继续变得更大。最终,它会溢出一个漫长而崩溃(Clojure的1.3)或促进自身为BigInteger和成长的所有堆(Clojure的1.2)

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

感谢。说得通。在第一种情况下,我可以调用第一个c,并且将评估无限列表中的第一个元素,它将保留在内存中。如果我第一次调用足够的次数,无限列表的评估部分将变得太大,堆将溢出。在第二种情况下,评估部分不断丢弃。顺便说一句,在第二种情况下,堆不会溢出,因为零的总和仍然为零。 – yalis 2011-03-08 03:37:24

+0

零点上的好点。想要提及的clojure 1.2和1.3在这方面是不同的,我猜它是错误的:) – 2011-03-08 04:35:39

11

亚瑟的回答是尽可能好,因为它去,但它的大小看起来他没有解决关于reductions的第二个问题。 reductions返回如果给定列表只有N个元素,则返回已返回的中间阶段的延迟序列。所以这是非常明智的调用reductions无限名单:

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

但是在这里,你最终会溢出堆。减少返回一个懒惰的列表,但要访问,例如,第百万元素,你必须评估第一百万元素,这可能会溢出堆。 – yalis 2011-03-08 03:39:43

+2

其实,回来。你可以继续调用next并保存列表的其余部分,放弃第一个元素。推理懒序列是棘手的业务。 – yalis 2011-03-08 03:46:21

+0

哦,这是neet的东西感谢增加。与任何懒惰的序列一样,一定要放松你的头;) – 2011-03-08 04:33:52

2

如果你想保持距离,如IO流的名单越来越项目,并保持运行之间的状态,你不能使用doseq(而不是诉诸def's)。相反,一个好的办法是使用环/复发这将让你避免消耗太多堆栈空间,将让你保持状态,你的情况:

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

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

+0

你可能想通过一些状态来模拟reduce。 (循环[c(循环[0])状态()(如果(评估某些条件(第一个c))(重复(休息c)do-something-with(第一(c)状态))状态) – yalis 2011-03-08 19:00:35

+1

pyr's版本将不会编译,除非'do-something-with'是一个扩展为尾部位置的'recur'实际*的形式的宏。 – amalloy 2011-05-12 06:36:18

0

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

至于这种情况的替代,这里有一个有用的功能,减少在一个序列只有前n项,使用复发合理的效率来实现:

(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 
+0

或者你可以使用(nth(减少+(范围))9999999) – yalis 2011-03-08 19:48:00

+0

非常真实。虽然(我的机器上的〜30%用于biggish n),但计数减少速度会更快一些。不完全确定为什么......但也许是因为减少产生的懒散的seq seq的额外开销? – mikera 2011-03-08 20:12:03