【发布时间】:2016-02-01 15:25:55
【问题描述】:
堆的算法枚举数组的排列。 Wikipedia's article on the algorithm 说 Robert Sedgewick 得出结论,该算法是“当时通过计算机生成排列的最有效算法”,因此尝试实现自然会很有趣。
该算法是关于在可变数组中进行连续交换,所以我正在考虑在 Clojure 中实现它,它的序列是不可变的。我将以下内容放在一起,完全避免了可变性:
(defn swap [a i j]
(assoc a j (a i) i (a j)))
(defn generate-permutations [v n]
(if (zero? n)
();(println (apply str a));Comment out to time just the code, not the print
(loop [i 0 a v]
(if (<= i n)
(do
(generate-permutations a (dec n))
(recur (inc i) (swap a (if (even? n) i 0) n)))))))
(if (not= (count *command-line-args*) 1)
(do (println "Exactly one argument is required") (System/exit 1))
(let [word (-> *command-line-args* first vec)]
(time (generate-permutations word (dec (count word))))))
对于 11 个字符的输入字符串,算法在 7.3 秒内(在我的计算机上)运行(平均超过 10 次运行)。
使用字符数组的等效 Java 程序运行时间为 0.24 秒。
所以我想让 Clojure 代码更快。我使用了带有类型提示的 Java 数组。这是我尝试过的:
(defn generate-permutations [^chars a n]
(if (zero? n)
();(println (apply str a))
(doseq [i (range 0 (inc n))]
(generate-permutations a (dec n))
(let [j (if (even? n) i 0) oldn (aget a n) oldj (aget a j)]
(aset-char a n oldj) (aset-char a j oldn)))))
(if (not= (count *command-line-args*) 1)
(do
(println "Exactly one argument is required")
(System/exit 1))
(let [word (-> *command-line-args* first vec char-array)]
(time (generate-permutations word (dec (count word))))))
嗯,它慢。现在,11 个字符的数组平均需要 9.1 秒(即使有类型提示)。
我知道可变数组不是 Clojure 的方式,但是有什么方法可以接近 Java 的这种算法的性能吗?
【问题讨论】:
-
您在比较中是否考虑了 JVM 预热/JIT?对我来说,通过
criterium/bench运行您的初始代码可以为 11 个字符的字符串提供大约 80-82 µs 的执行时间。阵列版本将其缩短至 59-60 µs。 -
这当然是很好的建议,谢谢你,虽然这里的预热比我预期的要多,因为使用 Java 的
System.currentTimeMillis和 Clojure 的time宏进行简单时钟时间测量之间的区别仍然比我预期的要广泛得多。关于criterium的好消息,我会研究一下。 -
当你应该这样做的时候使用CamelBack,我的眼睛会燃烧。
-
天哪,我很抱歉。已编辑。我希望你的眼睛能康复。是的,谢谢你的注意。我自己也经常对语言约定感到迂腐。
-
这是一个非常有趣的话题。我尝试了您的代码,并尝试对其进行调整,但没有太大改进。当我尝试优化它时,我实际上是在尝试优化算法,因此将算法分为“面向功能实现”和“面向命令实现”是合理的。
标签: arrays performance recursion clojure heaps-algorithm