在探索Clojure中计算阶乘的各种方法时,我提出了以下(非惯用)函数:
(defn factorial-using-do-dotimes [x]
(do
(def a 1)
(dotimes [i x]
(def a (* a (inc i)))))
a)
REPL:
user=> (factorial-using-do-dotimes 5)
120
这有什么具体的缺点(除了“非惯用”)?性能?正确性(即可能的缺陷)?
答案 0 :(得分:10)
如果您尝试并行运行此代码,则竞争条件可以为 导致它默默地产生错误的答案
首先自己运行它:
core> (factorial-using-do-dotimes 10)
3628800
然后运行两个副本,但让快速的一个提前完成:
core> (do (future (do (java.lang.Thread/sleep 5)
(factorial-using-do-dotimes 1200)))
(factorial-using-do-dotimes 10))
3628800
然后将它们拉近距离:
core> (do (future (do (java.lang.Thread/sleep 1)
(factorial-using-do-dotimes 1200)))
(factorial-using-do-dotimes 10))
3628800
然后同时运行它们:
core> (do (future (do (java.lang.Thread/sleep 0)
(factorial-using-do-dotimes 1200)))
(factorial-using-do-dotimes 10))
54698277723986154311681531904000000N
这不是正在运行的任何一个因子的答案 (factorial-using-do-dotimes 1200)长3176位
答案出错了。
注意:我更改了factorial-using-do-dotimes函数以使用*'
而不是*
,因此我可以在更大的示例上运行它,以便在示例中更容易命中时间。
答案 1 :(得分:3)
在函数体内部使用def不是惯用的,但不是明确的未定义行为,并且考虑到实现变量的方式,使用(dotimes .. (def ..))
可能比使用(loop ... (recur ...))
慢得多,特别是当你使用基本类型,如数字和类型提示时。
不对vars进行这种“动态”修改的主要原因是它没有任何理由使代码复杂化。循环/复发和瞬态的一些更惯用的组合应该在正常情况下使你获得性能,因为你可以摆脱clojure。它仍然是线程安全的,可预测的和可读的。
编辑:作为一个具体的缺陷:当同时从多个线程调用时,您的示例代码无法正常工作。
答案 2 :(得分:0)
重新绑定def可能会在需要同步的环境中产生不可预测的结果。如果需要修改状态,请考虑使用refs,atoms或agents。
您可以在for循环中累积值,如此
(defn chk-key-present-in-sos
""
[mapped-sos cmp-key cmp-seq-vals]
(if (seq-of-maps? mapped-sos)
(for [mapped-row mapped-sos
:let [matched-rows mapped-row]
:when (chk-one-val-present mapped-row cmp-key cmp-seq-vals)]
matched-rows)
nil))
您可以使用reduce来产生最终结果。
(defn key-pres?
"This function accepts a value (cmp-val) and a vector of vectors
(parsed output from clojure-csv) and returns the match value
back if found and nil if not found.
Using reduce, the function searches every vector row to see
if cmp-val is at the col-idx location in the vector."
[cmp-val cmp-idx csv-data]
(reduce
(fn [ret-rc csv-row]
(if (= cmp-val (nth csv-row col-idx nil))
(conj ret-rc cmp-val)))
[]
csv-data))
您可以使用into
累积。
最后,您始终可以使用递归并让终止步骤返回 累积的斐波纳契值。