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