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

时间:2021-08-02 03:30:19

I'm using Clojure and I need to run a small simulation. I have a vector of length n (n is usually between 10 and 100) that holds values. On each simulation round (maybe 1000 rounds together), one of the values in the vector is updated randomly. I guess I could do this by using an Java array and calling the aset method, but this would break the functional programming/immutability idiom.

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

Is there a more functional way to do this, or should I just go with the Java array?

有没有更实用的方法来实现这一点,还是应该使用Java数组?

4 个解决方案

#1


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]

There's no shame in using a Java array if you need it though. Especially if you need it to go fast. Limit the array-mutation to the inside of your function (clone the input array and work on that maybe) and no one will be the wiser.

如果你需要它,使用Java数组并不羞耻。特别是如果你需要它快速。将数组变异限制在函数内部(克隆输入数组并对其进行处理)并且没有人会更聪明。

#2


5  

Adding to Brian's answer: If you need more speed, you can also resort to 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))))))

#3


2  

Lets first define a function which updates a random index in a vector with a new value. Note that the original vector is not changed, instead a new vector (with the updated value) is returned:

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

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

This function chooses an index and then it either increases or decreases the value at that index by 1. You must of course change this function to your needs.

此函数选择一个索引,然后将该索引处的值增加或减少1.您当然必须根据需要更改此函数。

Then it is a simple matter to compose this function with itself as many times you want to run the simulation:

然后,只要你想运行模拟,就可以将这个函数与自身组合起来很简单:

user=> ((apply comp (repeat 1000 f)) [0 0 0 0 0 0 0])
[7 -4 7 6 10 0 -6]

#4


1  

It's not that Clojure won't let you change values, it's just a little more cumbersome.

这并不是说Clojure不会让你改变价值观,它只是有点麻烦。

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

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

to look at values in the changed vector, use @vec-ref.

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

Could be off in details - I'm not near a REPL, unfortunately. But it should get you started.

可能是关闭的细节 - 不幸的是,我不在REPL附近。但它应该让你开始。

#1


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]

There's no shame in using a Java array if you need it though. Especially if you need it to go fast. Limit the array-mutation to the inside of your function (clone the input array and work on that maybe) and no one will be the wiser.

如果你需要它,使用Java数组并不羞耻。特别是如果你需要它快速。将数组变异限制在函数内部(克隆输入数组并对其进行处理)并且没有人会更聪明。

#2


5  

Adding to Brian's answer: If you need more speed, you can also resort to 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))))))

#3


2  

Lets first define a function which updates a random index in a vector with a new value. Note that the original vector is not changed, instead a new vector (with the updated value) is returned:

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

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

This function chooses an index and then it either increases or decreases the value at that index by 1. You must of course change this function to your needs.

此函数选择一个索引,然后将该索引处的值增加或减少1.您当然必须根据需要更改此函数。

Then it is a simple matter to compose this function with itself as many times you want to run the simulation:

然后,只要你想运行模拟,就可以将这个函数与自身组合起来很简单:

user=> ((apply comp (repeat 1000 f)) [0 0 0 0 0 0 0])
[7 -4 7 6 10 0 -6]

#4


1  

It's not that Clojure won't let you change values, it's just a little more cumbersome.

这并不是说Clojure不会让你改变价值观,它只是有点麻烦。

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

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

to look at values in the changed vector, use @vec-ref.

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

Could be off in details - I'm not near a REPL, unfortunately. But it should get you started.

可能是关闭的细节 - 不幸的是,我不在REPL附近。但它应该让你开始。