math.combinatorics的文档指出所有函数都返回延迟序列。
但是,如果我尝试使用大量数据运行subsets,
(last (combinatorics/subsets (range 20)))
;OutOfMemoryError Java heap space clojure.lang.RT.cons (RT.java:559)
我收到OutOfMemory错误。
运行
(last (range))
烧毁CPU,但不会返回错误。
Clojure似乎并不像解释in this Stack Overflow question那样“保持头脑”。
为什么会发生这种情况以及如何在子集中使用更大的范围?
更新
正如评论所暗示的那样,它似乎适用于某些人的计算机。所以我将发布我的系统配置
我运行Mac(10.8.3)并使用Clojure安装Homebrew(1.5.1)。
我的Java版本是:
% java -version
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06-451-11M4406)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01-451, mixed mode)
我没有更改任何默认设置。我还通过删除~/.m2
文件夹重新安装了所有依赖项。
我的projects.clj。
我使用的命令就是这个
% lein repl
nREPL server started on port 61774
REPL-y 0.1.10
Clojure 1.5.1
=> (require 'clojure.math.combinatorics)
nil
=> (last (clojure.math.combinatorics/subsets (range 20)))
OutOfMemoryError Java heap space clojure.lang.RT.cons (RT.java:570)
or
OutOfMemoryError Java heap space clojure.math.combinatorics/index-combinations/fn--1148/step--1164 (combinatorics.clj:64)
我在同事的笔记本电脑上测试了这个问题,他遇到了同样的问题,但他也在Mac上。
答案 0 :(得分:3)
问题在于subsets
使用mapcat
,mapcat
不够懒,因为它使用了apply,它实现并保存了一些要连接的元素。见a very nice explanation here。在子集中使用该链接的lazier mapcat版本应解决问题:
(defn my-mapcat
[f coll]
(lazy-seq
(if (not-empty coll)
(concat
(f (first coll))
(my-mapcat f (rest coll))))))
(defn subsets
"All the subsets of items"
[items]
(my-mapcat (fn [n] (clojure.math.combinatorics/combinations items n))
(range (inc (count items)))))
(last (subsets (range 50))) ;; this will take hours to compute, good luck with it!
答案 1 :(得分:3)
您想要计算具有1000个元素的集合的幂集吗?你知道那将有2 ^ 1000个元素,对吧?这是如此之大,我甚至找不到一个很好的方式来描述它是多么巨大。如果您正在尝试使用这样的一组,并且您可以懒惰地这样做,那么您的问题将不是内存:它将是计算时间。假设您拥有一台具有无限内存的超级计算机,每纳秒可以处理万亿项目:每秒处理10 ^ 21项,或每年约10 ^ 29项。即使这台超级计算机也需要比宇宙的生命周期更长的时间来处理(subsets (range 1000))
的项目。
所以我要说,不要再担心这个集合的内存使用情况了,并且研究一种算法,该算法不涉及遍历具有比宇宙中的原子更多元素的序列。
答案 2 :(得分:2)
问题既不是apply
,也不是concat
,也不是mapcat
。
dAni's answer,他重新实现mapcat
,意外导致解决问题,但其背后的推理并不正确。此外,他的回答指向一篇文章,其中作者说“我相信问题在于应用”。 这显然是错误的,因为我将在下面解释。最后,手头的问题与this other one无关,apply
导致非懒惰的评估确实。
如果仔细观察,dAni和该文章的作者都会在不使用mapcat
函数的情况下实现map
。我将在下一个示例中显示该问题与map
函数的实现方式有关。
要证明该问题与apply
或concat
无关,请参阅mapcat
的以下实施。它同时使用concat
和apply
,但仍会实现完全懒惰:
(defn map
([f coll]
(lazy-seq
(when-let [s (seq coll)]
(cons (f (first s)) (map f (rest s)))))))
(defn mapcat [f & colls]
(apply concat (apply map f colls)))
(defn range-announce! [x]
(do (println "Returning sequence of" x)
(range x)))
;; new fully lazy implementation prints only 5 lines
(nth (mapcat range-announce! (range)) 5)
;; clojure.core version still prints 32 lines
(nth (clojure.core/mapcat range-announce! (range)) 5)
通过重新实现map
函数,可以实现上述代码中的完全惰性。实际上,mapcat
与clojure.core
一样exactly the same way已实现,但它完全是懒惰的。上面的map
实现在示例中有点简化,因为它只支持单个参数,但即使用整个可变参数签名实现它也会有相同的效果:完全懒惰 。因此,我们发现此处的问题既不是apply
也不是concat
。此外,我们发现真正的问题必须与map
中clojure.core
函数的实现方式有关。我们来看看它:
(defn map
([f coll]
(lazy-seq
(when-let [s (seq coll)]
(if (chunked-seq? s)
(let [c (chunk-first s)
size (int (count c))
b (chunk-buffer size)]
(dotimes [i size]
(chunk-append b (f (.nth c i))))
(chunk-cons (chunk b) (map f (chunk-rest s))))
(cons (f (first s)) (map f (rest s))))))))
可以看出clojure.core
实现与之前的“简化”版本完全相同,除了true
表达式的if (chunked-seq? s)
分支。基本上clojure.core/map
有一个处理 chunked sequences 的输入序列的特殊情况。
分块序列通过评估32个块而不是一次严格一个来破坏懒惰。在评估深层嵌套的分块序列时,这变得非常明显,就像subsets
的情况一样。在Clojure 1.1中引入了分块序列,并且升级了许多核心功能以识别和处理它们,包括map
。引入它们的主要目的是提高某些流处理场景的性能,但可以说它们使得更难以推断程序的懒惰特征。您可以阅读分块序列here和here。另请查看此问题here。
真正的问题是range
返回一个chunked seq,并由subsets
在内部使用。 David James修补程序subsets
建议 unchunk 内部由range
创建的序列。
答案 3 :(得分:1)
此问题已在项目的故障单跟踪器中提出:Clojure JIRA: OutOfMemoryError with combinatorics/subsets。在那里,你可以找到Andy Fingerhut的补丁。它对我有用。请注意,该修补程序与mapcat variation suggested by another answer不同。
答案 4 :(得分:0)
在没有命令行参数的情况下,JVM的启动堆大小参数由各种人体工程学确定
默认值(JDK 6)
initial heap size memory / 64 maximum heap size MIN(memory / 4, 1GB)
但您可以使用-Xmx
和-Xms
args强制使用绝对值
您可以找到更多详细信息here