我非常喜欢关于Frank Nelson Cole的故事,他在1903年在一个着名的“没有语言的演讲”中展示了2 ^ 67 - 1的素数因子化。目前,使用以下天真算法可以很容易地找到因子分解:
(def mersenne67 (dec (expt 2 67)))
(->> (iterate inc 2)
(filter #(zero? (rem mersenne67 %)))
(first))
但是我注意到这个Clojure代码大约是等效Java或Kotlin代码的两倍。 (我机器上约40秒~20秒)
这是我将它与之比较的Java:
public static BigInteger mersenne() {
BigInteger mersenne67 =
BigInteger.valueOf(2).pow(67).subtract(BigInteger.ONE);
return Stream.iterate(BigInteger.valueOf(2), (x -> x.add(BigInteger.ONE)))
.filter(x -> mersenne67.remainder(x).equals(BigInteger.ZERO))
.findFirst()
.get();
}
在较低级别重写Clojure代码没有任何区别:
(def mersenne67 (-> (BigInteger/valueOf 2)
(.pow (BigInteger/valueOf 67))
(.subtract BigInteger/ONE)))
(->> (iterate #(.add ^BigInteger % BigInteger/ONE) (BigInteger/valueOf 2))
(filter #(= BigInteger/ZERO (.remainder ^BigInteger mersenne67 %)))
(first))
使用VisualVM分析代码后,主要嫌疑人似乎是clojure.lang.Iterate.first()
,这几乎完全解释了这些函数运行的时间差异。 Java的等效java.util.stream.ReferencePipeline.findFirst()
只运行一小部分(~22 vs~2秒)。
这引出了我的问题:Java(和Kotlin)如何在这项任务上花费更少的时间?
答案 0 :(得分:2)
您的问题是您无法有效地迭代iterate
。这就是您在分析时看到first
的原因。当然,这是clojure的所有核心功能与大量不同数据结构一起工作的结果。
避免这种情况的最好方法是使用reduce
,它为对象本身提供了在循环中调用函数的任务。
所以这大约快2倍:
(reduce
(fn [_ x]
(when (identical? BigInteger/ZERO (.remainder ^BigInteger mersenne67 x))
(reduced x)))
nil
(iterate #(.add ^BigInteger % BigInteger/ONE) (BigInteger/valueOf 2)))
答案 1 :(得分:1)
我提前为严肃挖掘道歉,但我有点担心ClojureMostly的回答。它肯定能及时解决问题,但对我来说它看起来像是一个肮脏的黑客:传递匿名减少函数,它忽略当前结果(_)并在第一个因子被找到(减少)后立即结束。
如何使用传感器和转换功能:
(defn first-factor-tr
[^BigInteger n]
(transduce
(comp (filter #(identical? BigInteger/ZERO (.remainder ^BigInteger n %))) (take 1))
conj nil
(iterate #(.add ^BigInteger % BigInteger/ONE) (BigInteger/valueOf 2))))
过滤掉集合中的所有值,余数为零(过滤器...)并取第一个(取......)。
此解决方案的执行时间与reduce:
的执行时间相同(defn first-factor-reduce
[^BigInteger n]
(reduce
(fn [_ x]
(when (identical? BigInteger/ZERO (.remainder ^BigInteger n x))
(reduced x)))
nil
(iterate #(.add ^BigInteger % BigInteger/ONE) (BigInteger/valueOf 2))))
=> (time (first-factor-tr mersenne67))
"Elapsed time: 20896.594178 msecs"
(193707721)
=> (time (first-factor-reduce mersenne67))
"Elapsed time: 20424.323894 msecs"
193707721