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 ответа

Решение

Как я понимаю ваши требования вы хотите

  1. Создайте ряд последовательностей клавиш из m1,
  2. В 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.

Другие вопросы по тегам