3

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

これの具体的な欠点は何ですか(「非慣用的」を除く)?パフォーマンス?正しさ(つまり、考えられる欠陥)?

4

3 に答える 3

10

このコードを並行して実行しようとすると、競合状態
により、静かに間違った答えが生成される可能性があります

最初にそれを単独で実行します。

core> (factorial-using-do-dotimes 10)
3628800                                                                                                    

次に、2つのコピーを実行しますが、高速のコピーをかなり前に終了させます。

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関数を*'代わりに使用する*ように変更したので、より大きな例で実行して、例でタイミングを打ちやすくすることができました。

于 2012-08-23T22:41:53.297 に答える
3

Using def inside a function body is not idiomatic, sort-of-but-not-explicitly undefined behavior, and considering the way vars are implemented, using (dotimes .. (def ..)) is quite possibly slower than using (loop ... (recur ...)), especially when you're using basic types, like numbers, and type-hints.

The main reason not to do this kind of "dynamic" modification of vars, is that it complicates your code for no good reason. Some much more idiomatic combination of loop/recur and transients should under normal circumstances get you as good a performance as you can get out of clojure. And it will still be thread-safe, predictable and readable.

Edit: as a concrete defect: your example code does not work correctly when called from multiple threads concurrently.

于 2012-08-23T20:55:43.047 に答える
0

Rebinding a def can have unpredictable results in an environment that requires synchronization. Consider using refs, atoms, or agents, if you need to modify state.

You can accumulate values in a for loop like so

(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))

You can use reduce to produce a final result.

(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))

And you can use into to accumulate.

Finally, you can always use recursion and let the termination step return the accumulated Fibonacci value.

于 2012-08-23T20:55:44.570 に答える