我正在使用Clojure,需要运行一个小型模拟。我有一个长度为n的向量(n通常在10到100之间),保存着一些值。在每一轮模拟(总共大概1000轮),向量中的一个值会被随机更新。我想我可以通过使用Java数组并调用aset方法来实现这一点,但这会破坏函数式编程/不可变性习惯。
是否有更多函数式的方法来实现这个,或者我应该只使用Java数组?
是否有更多函数式的方法来实现这个,或者我应该只使用Java数组?
(defn run-sim [arr num-iters update-fn]
(if (zero? num-iters)
arr
(let [i (rand-int (count arr))
x (update-fn)]
(println "setting arr[" i "] to" x)
(recur (assoc arr i x) (dec num-iters) update-fn))))
user> (run-sim [1 2 3 4 5 6 7 8 9 10] 10 #(rand-int 1000))
setting arr[ 8 ] to 167
setting arr[ 4 ] to 977
setting arr[ 5 ] to 810
setting arr[ 5 ] to 165
setting arr[ 3 ] to 486
setting arr[ 1 ] to 382
setting arr[ 4 ] to 792
setting arr[ 8 ] to 478
setting arr[ 4 ] to 144
setting arr[ 7 ] to 416
[1 382 3 486 144 165 7 416 478 10]
如果需要,使用Java数组并没有什么可羞耻的。特别是如果您需要它快速运行。将数组变异限制在函数内部(可能克隆输入数组并在其上工作),则不会有人知道。
补充Brian的回答:如果你需要更快的速度,你也可以使用transients。
(defn run-sim
[vektor num-iters update-fn]
(loop [vektor (transient vektor)
num-iters (int num-iters)]
(if (zero? num-iters)
(persistent! vektor)
(let [i (rand-int (count vektor))
x (update-fn)]
(recur (assoc! vektor i x) (dec num-iters))))))
首先定义一个函数,该函数用新值更新向量中的随机索引。请注意,原始向量不会改变,而是返回一个新向量(带有更新的值):
(defn f [xs]
(let [r (java.util.Random.)
i (.nextInt r (count xs))
b (.nextBoolean r)]
(assoc xs i ((if b inc dec) (xs i)))))
这个函数会选择一个索引,然后将该索引处的值增加或减少1。当然,您必须根据自己的需求更改此函数。
然后,将此函数与自身组合多次以运行模拟就变得非常简单了:
user=> ((apply comp (repeat 1000 f)) [0 0 0 0 0 0 0])
[7 -4 7 6 10 0 -6]
不是Clojure不允许你改变值,只是稍微麻烦一点。
(def vec-ref (ref my-vector))
(dosync (set! vec-ref (assoc my-vector index value))
要查看更改后的向量中的值,请使用@vec-ref。
可能会有一些细节错误 - 不幸的是,我不在REPL附近。但这应该可以让你开始了。