3

在探索 Clojure 中计算阶乘的各种方法时,我想出了以下(非惯用的)函数:

(defn factorial-using-do-dotimes [x]
  (do
    (def a 1)
    (dotimes [i x]
      (def a (* a (inc i)))))
  a)

回复:

user=> (factorial-using-do-dotimes 5)
120

这有什么具体的缺点(除了“非惯用的”)?表现?正确性(即可能的缺陷)?

4

3 回答 3

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 函数更改为 use*'而不是,*因此我可以在更大的示例上运行它,以使示例中的时间更容易命中。

于 2012-08-23T22:41:53.297 回答
3

在函数体内使用 def 不是惯用的,排序但不是明确未定义的行为,并且考虑到 vars 的实现方式, using(dotimes .. (def ..))很可能比 using 慢(loop ... (recur ...)),尤其是当您使用基本类型时,例如数字,和类型提示。

不对变量进行这种“动态”修改的主要原因是它无缘无故地使您的代码复杂化。在正常情况下,循环/递归和瞬态的一些更惯用的组合应该可以让您获得与 clojure 一样好的性能。它仍然是线程安全的、可预测的和可读的。

编辑:作为一个具体的缺陷:当从多个线程同时调用时,您的示例代码无法正常工作。

于 2012-08-23T20:55:43.047 回答
0

在需要同步的环境中,重新绑定 def 可能会产生不可预知的结果。如果需要修改状态,请考虑使用 refs、atoms 或 agent。

您可以像这样在 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用来积累。

最后,您始终可以使用递归并让终止步骤返回累积的斐波那契值。

于 2012-08-23T20:55:44.570 回答