Как сделать численное моделирование с неизменными данными в Clojure? - PullRequest
7 голосов
/ 17 ноября 2009

Я использую Clojure и мне нужно запустить небольшую симуляцию. У меня есть вектор длины n (n обычно между 10 и 100), который содержит значения. В каждом раунде симуляции (возможно, 1000 раундов вместе) одно из значений в векторе обновляется случайным образом. Полагаю, я мог бы сделать это, используя массив Java и вызвав метод aset, но это нарушило бы функциональность программирования / неизменности.

Есть ли более функциональный способ сделать это, или я должен просто пойти с массивом Java?

Ответы [ 4 ]

6 голосов
/ 17 ноября 2009
(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 голосов
/ 17 ноября 2009

Добавление к ответу Брайана: если вам нужна большая скорость, вы также можете прибегнуть к переходным процессам.

(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))))))
2 голосов
/ 17 ноября 2009

Давайте сначала определим функцию, которая обновляет случайный индекс в векторе новым значением. Обратите внимание, что исходный вектор не изменяется, вместо этого возвращается новый вектор (с обновленным значением):

(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]
1 голос
/ 17 ноября 2009

Дело не в том, что Clojure не позволит вам изменять значения, это просто немного более громоздко.

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

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

чтобы посмотреть значения в измененном векторе, используйте @ vec-ref.

Может быть в деталях - к сожалению, я не нахожусь рядом с REPL. Но это должно помочь вам начать.

...