Объединить список карт и объединить значения с наборами в Clojure
Какую функцию я могу поставить как FOO здесь, чтобы дать true в конце? Я играл с хэш-множеством (только правильно для первых 2 значений), conj и concat, но я знаю, что я не обрабатываю одноэлементное условие установки с любым из них.
(defn mergeMatches [propertyMapList]
"Take a list of maps and merges them combining values into a set"
(reduce #(merge-with FOO %1 %2) {} propertyMapList))
(def in
(list
{:a 1}
{:a 2}
{:a 3}
{:b 4}
{:b 5}
{:b 6} ))
(def out
{ :a #{ 1 2 3}
:b #{ 4 5 6} })
; this should return true
(= (mergeMatches in) out)
Каков самый идиоматический способ справиться с этим?
Ответы
Ответ 1
Это сделает:
(let [set #(if (set? %) % #{%})]
#(clojure.set/union (set %) (set %2)))
Переписано более непосредственно для примера (Alex):
(defn to-set [s]
(if (set? s) s #{s}))
(defn set-union [s1 s2]
(clojure.set/union (to-set s1) (to-set s2)))
(defn mergeMatches [propertyMapList]
(reduce #(merge-with set-union %1 %2) {} propertyMapList))
Ответ 2
Я бы не использовал merge-with для этого,
(defn fnil [f not-found]
(fn [x y] (f (if (nil? x) not-found x) y)))
(defn conj-in [m map-entry]
(update-in m [(key map-entry)] (fnil conj #{}) (val map-entry)))
(defn merge-matches [property-map-list]
(reduce conj-in {} (apply concat property-map-list)))
user=> (merge-matches in)
{:b #{4 5 6}, :a #{1 2 3}}
fnil скоро станет частью ядра, поэтому вы можете игнорировать реализацию... но он просто создает версию другой функции, которая может обрабатывать аргументы nil. В этом случае conj будет заменять # {} на nil.
Итак, редукция, соединяющая набор для каждого ключа/значения в списке прилагаемых карт.
Ответ 3
Я не писал это, но внес в @amitrathore на Twitter:
(defn kv [bag [k v]]
(update-in bag [k] conj v))
(defn mergeMatches [propertyMapList]
(reduce #(reduce kv %1 %2) {} propertyMapList))
Ответ 4
Другое решение, внесенное @wmacgyver в Twitter на основе multimaps:
(defn add
"Adds key-value pairs the multimap."
([mm k v]
(assoc mm k (conj (get mm k #{}) v)))
([mm k v & kvs]
(apply add (add mm k v) kvs)))
(defn mm-merge
"Merges the multimaps, taking the union of values."
[& mms]
(apply (partial merge-with union) mms))
(defn mergeMatches [property-map-list]
(reduce mm-merge (map #(add {} (key (first %)) (val (first %))) property-map-list)))
Ответ 5
Не очень красивая, но она работает.
(defn mergeMatches [propertyMapList]
(for [k (set (for [pp propertyMapList] (key (first pp))))]
{k (set (remove nil? (for [pp propertyMapList] (k pp))))}))
Ответ 6
Это работает:
(defn FOO [v1 v2]
(if (set? v1)
(apply hash-set v2 v1)
(hash-set v1 v2)))