如何在Clojure中使用不可变数据进行数字模拟?

7
我正在使用Clojure,需要运行一个小型模拟。我有一个长度为n的向量(n通常在10到100之间),保存着一些值。在每一轮模拟(总共大概1000轮),向量中的一个值会被随机更新。我想我可以通过使用Java数组并调用aset方法来实现这一点,但这会破坏函数式编程/不可变性习惯。
是否有更多函数式的方法来实现这个,或者我应该只使用Java数组?
4个回答

6
(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数组并没有什么可羞耻的。特别是如果您需要它快速运行。将数组变异限制在函数内部(可能克隆输入数组并在其上工作),则不会有人知道。


5

补充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))))))

谢谢!我得深入研究一下那些瞬态东西,速度总是很重要的! - user212796
1
暂存对象的使用很简单:在开始时调用(transient thing)。对操作添加感叹号,例如assoc!、dissoc!等,返回时调用(persistent! transient-thing)。然而你不应该在函数外泄露暂存对象。 - kotarak

2

首先定义一个函数,该函数用新值更新向量中的随机索引。请注意,原始向量不会改变,而是返回一个新向量(带有更新的值):

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

谢谢你的回答,这种组合方法看起来也很有趣。 - user212796

1

不是Clojure不允许你改变值,只是稍微麻烦一点。

(def vec-ref (ref my-vector))

(dosync (set! vec-ref (assoc my-vector index value))

要查看更改后的向量中的值,请使用@vec-ref。

可能会有一些细节错误 - 不幸的是,我不在REPL附近。但这应该可以让你开始了。


谢谢,我也会检查这种解决问题的方法。 - user212796

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接