Как выполнить численное моделирование с неизменяемыми данными в Clojure?

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

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


person Community    schedule 17.11.2009    source источник


Ответы (4)


(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, если он вам нужен. Особенно, если нужно быстро. Ограничьте мутацию массива внутри вашей функции (клонируйте входной массив и, возможно, поработайте над этим), и никто не станет мудрее.

person Brian Carper    schedule 17.11.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))))))
person kotarak    schedule 17.11.2009
comment
Благодарю вас! Мне придется покопаться в этом переходном материале, скорость всегда хороша! - person ; 17.11.2009
comment
Переходные процессы просты: вызов (переходная вещь) в начале. Добавить ! к операциям, напр. ассоц!, диссок! и т. д. вызов (постоянный! переходный-вещь) при возврате. Однако вы не должны пропускать переходные процессы за пределы вашей функции. - person kotarak; 17.11.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]
person Jonas    schedule 17.11.2009
comment
Спасибо за ответ, этот compose-подход тоже выглядит интересно. - person ; 17.11.2009

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

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

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

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

В деталях могу ошибаться - я не рядом с REPL, к сожалению. Но это должно заставить вас начать.

person Carl Smotricz    schedule 17.11.2009
comment
Спасибо, тоже проверю этот способ решения проблемы. - person ; 17.11.2009