Clojure: перебрать карту множеств
Это в значительной степени продолжение моего последнего вопроса ( Clojure идиоматический способ обновления нескольких значений карты), но не совсем то же самое. (имейте в виду, что я довольно плохо знаком с Clojure и функциональными языками)
Предположим, у меня есть следующая структура данных, определенная как карта наборов:
(def m1 {:1 #{2} :2 #{1 3} :3 #{1}})
и карта карт как таковая:
(def m2 {:1 {:1 0 :2 12 :3 23} :2 {:1 23 :2 0 :3 4} :3 {:1 2 :2 4 :3 0}})
Что я хочу сделать, это обновить реестры m2
которые соответствуют в m1 определенному значению. Скажем, ценность, которую я хочу x
, Результирующий m2
будет что-то вроде этого:
{:1 {:1 0 :2 x :3 23} :2 {:1 x :2 0 :3 x} :3 {:1 x :2 4 :3 0}}
При условии, что v
содержит все возможные ключи для моей карты, у первой попытки (что я потерпел неудачу) является сделать что-то вроде этого: (предположим, что x=1
(for [i v]
reduce (fn [m j] (assoc-in m [i j] 1)) d (i m1)))
Излишне говорить, что это был провал. Итак, как идиоматический способ сделать это?
3 ответа
Как я понимаю ваши требования вы хотите
- Создайте ряд последовательностей клавиш из
m1
, - В
m2
свяжите каждую из этих последовательностей клавиш с определенным постоянным значением.
Первый шаг - довольно простое преобразование m1
, Мы хотим произвести 0 или более последовательностей клавиш для каждой записи (в зависимости от того, сколько в ее наборе), поэтому естественный переход для меня mapcat
, Это означает map
-затем-concat
и отлично подходит именно для такого случая, когда из каждого элемента последовательности мы производим 0 или более элементов, которые мы хотим.
(defn key-seqs [coll]
(mapcat
(fn [[k v]]
(map (partial vector k) v))
coll))
(key-seqs m1)
;;=> ([:1 2] [:2 1] [:2 3] [:3 1])
Обратите внимание, что функция взята mapcat
само по себе map
, так что он производит последовательность (возможно, пустую) для mapcat
развернуть. Но это возвращает длинные значения, хранящиеся в наборах, как самих себя. Если мы хотим превратить их в ключевые слова, чтобы соответствовать m2
нам нужно немного больше обработки:
(defn key-seqs [coll]
(mapcat
(fn [[k v]]
(map (comp (partial vector k) keyword str) v))
coll))
(key-seqs m1)
;;=> ([:1 :2] [:2 :1] [:2 :3] [:3 :1])
(Нам нужно использовать str
так как keyword
не знает, что делать с длинными Обычно ключевые слова - это не числа, а имена с некоторым символическим значением)
Тогда мы можем адаптировать update-m
от вашего предыдущего вопроса немного, чтобы он мог принять постоянное значение в качестве аргумента и обрабатывать последовательности клавиш, которые не просто имеют одно и то же значение дважды:
(defn update-m [m x v]
(reduce (fn [m' key-seq]
(assoc-in m' key-seq x)) ;; accumulate changes
m ;; initial-value
v)) ;; collection to loop over
и теперь мы как будто в деле
(update-m m2 1 (key-seqs m1))
;;=> {:1 {:1 0, :2 1, :3 23}, :2 {:1 1, :2 0, :3 1}, :3 {:1 1, :2 4, :3 0}}
Я думаю, что хорошим решением было бы, если бы вы изменили структуру данных m1 на что-то вроде
(def m1-new [[:1 :2] [:2 :1] [:2 :3] [:3 :1]])
Тогда вы можете просто reduce
над этим и использовать assoc-in
,
(reduce (fn [m path] (assoc-in m path my-new-value)) m2 m1-new)
Попробуйте это (здесь х равен 100)
(merge-with merge m2
(into {} (for [[k v] m1] [k (into {} (for [i v] [(keyword (str i)) 100]))])))
РЕДАКТИРОВАТЬ:
Идея заключается в следующем:
- Конвертировать m1 из
{:1 #{2} :2 #{1 3} :3 #{1}}
в{:1 {:2 x} :2 {:1 x :3 x} :3 {:1 x}}
который в основном конвертирует каждый набор в карту, где ключ - это значения набора, а значения - это постоянная x. - Объедините и m2 и новый m1.
ПРИМЕЧАНИЕ. Существует предположение, что все ключи в m1 указаны в м2.