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

Это, вероятно, просто, но я просто не могу с этим справиться. У меня есть структура данных, которая является вложенной картой, например:

(def m {:1 {:1 2 :2 5 :3 10} :2 {:1 2 :2 50 :3 25} :3 {:1 42 :2 23 :3 4}})

Мне нужно установить каждый m[i][i]=0, Это просто в нефункциональных языках, но я не могу заставить его работать на Clojure. Как идиоматический способ сделать это, учитывая, что у меня есть вектор с каждым возможным значением? (давайте назовем это v)

дела (map #(def m (assoc-in m [% %] 0)) v) будет работать, но используя def внутри функции на map не кажется правильным. Превращение m в атомарную версию и использование swap! кажется лучше. Но не сильно. Это также кажется ДЕЙСТВИТЕЛЬНО медленным.

(def am (atom m))
(map #(swap! am assoc-in[% %] 0) v)

Каков наилучший / правильный способ сделать это?

ОБНОВИТЬ

Некоторые отличные ответы здесь. Я разместил дополнительный вопрос здесь Clojure: перебираем карту наборов, которая тесно связана, но не так сильно, с этим.

3 ответа

Решение

Вы правы, что это плохая форма для использования def внутри функции. Также плохо использовать функции с побочными эффектами (такими как swap) внутри map, Furthemore, map ленивый, так что ваш map/swap попытка на самом деле ничего не сделает, если она не вызвана, например, dorun,

Теперь, когда мы рассмотрели, что не нужно делать, давайте посмотрим, как это сделать;-).

Есть несколько подходов, которые вы можете использовать. Возможно, для кого-то проще всего начать с обязательной парадигмы: loop:

(defn update-m [m v]
  (loop [v' v
         m' m]
    (if (empty? v')
      ;; then (we're done => return result)
      m'
      ;; else (more to go => process next element)
      (let [i (first v')]
        (recur (rest v')                  ;; iterate over v
               (assoc-in m' [i i] 0)))))) ;; accumulate result in m'

Тем не мение, loop является относительно низкоуровневой конструкцией в рамках функциональной парадигмы. Здесь мы можем отметить шаблон: мы перебираем элементы v и накапливая изменения в m', Эта картина фиксируется reduce функция:

(defn update-m [m v]
  (reduce (fn [m' i]
            (assoc-in m' [i i] 0)) ;; accumulate changes
          m   ;; initial-value
          v)) ;; collection to loop over

Эта форма немного короче, потому что ей не нужен код котельной loop форма требует. reduce Поначалу форма может быть не так проста для чтения, но как только вы привыкнете к функциональному коду, она станет более естественной.

Теперь, когда у нас есть update-mМы можем использовать его для преобразования карт в программе в целом. Например, мы можем использовать его для swap! атом. Следуя вашему примеру выше:

(swap! am update-m v)

С помощью def внутри функции, конечно, не рекомендуется. ОО способ взглянуть на проблему - заглянуть внутрь структуры данных и изменить значение. Функциональным способом решения проблемы является создание новой структуры данных, которая представляет старую структуру с измененными значениями. Таким образом, мы могли бы сделать что-то вроде этого

(into {} (map (fn [[k v]] [k (assoc v k 0)]) m))

То, что мы делаем здесь, это картирование m во многом так же, как вы сделали в своем первом примере. Но тогда вместо модификации m мы возвращаем [] кортеж ключа и значения. Этот список кортежей мы можем затем вернуть на карту.

Другие ответы хороши, но для полноты приведу немного более короткую версию с for понимание. Я нахожу это более читабельным, но это вопрос вкуса:

(into {} (for [[k v] m] {k (assoc v k 0)}))
Другие вопросы по тегам