7

我正在使用 Clojure,我需要运行一个小型模拟。我有一个长度为 n 的向量(n 通常在 10 到 100 之间),其中包含值。在每一轮模拟中(可能一共 1000 轮),向量中的一个值会随机更新。我想我可以通过使用 Java 数组并调用 aset 方法来做到这一点,但这会破坏函数式编程/不变性习语。

有没有更实用的方法来做到这一点,或者我应该只使用 Java 数组?

4

4 回答 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 数组并不丢人。特别是如果您需要它快速运行。将数组突变限制在函数内部(克隆输入数组并可能进行处理),没有人会更聪明。

于 2009-11-17T11:55:21.433 回答
5

添加到布赖恩的回答:如果你需要更快的速度,你也可以诉诸瞬变。

(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))))))
于 2009-11-17T12:29:21.617 回答
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]
于 2009-11-17T12:26:29.157 回答
1

并不是说 Clojure 不会让你改变值,只是稍微麻烦一些。

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

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

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

可能会详细说明-不幸的是,我不在REPL附近。但它应该让你开始。

于 2009-11-17T11:21:23.650 回答