2012-06-27 7 views
5

Sto tentando di unire idiomaticamente più mappe in una singola mappa usando clojure.unire le mappe in una mappa di set di valori con clojure

ingresso

{:a 1 :b "a"} 
{:a 2 :b "b"} 
{:a 3 :b "c"} 
{:a 4 :b "a"} 

Expected

{:a #{1,2,3,4}, :b #{"a" "b" "c"}} 

I valori per ogni chiave vengono convertiti in un insieme di valori nelle mappe originali.

risposta

5

userei merge-with, utilizzando un pre-costruito struttura che contiene insieme vuoto:

(def data [{:a 1 :b "a"} 
      {:a 2 :b "b"} 
      {:a 3 :b "c"} 
      {:a 4 :b "a"}]) 

(let [base {:a #{} :b #{}}] 
    (apply merge-with conj base data)) 

=> {:a #{1 2 3 4}, :b #{"a" "b" "c"}} 

Il trucco di usare insieme vuoto in la mappa di base è così che lo conj ha un oggetto concreto su cui lavorare, e quindi funziona correttamente.

+0

Questo funziona, ma solo se si conoscono in anticipo tutte le chiavi corrispondenti. Una limitazione da tenere a mente. – amalloy

4

merge-with può essere utilizzato per questo tipo:

(def d [{:a 1 :b "a"} 
     {:a 2 :b "b"} 
     {:a 3 :b "c"} 
     {:a 4 :b "a"}]) 

(def initial (into {} (map #(vector %1 []) (keys (apply merge d))))) 

(into {} (map (fn [[a b]] [a (set b)]) 
      (apply merge-with (fn [a b] (conj a b)) initial d))) 
+1

Vi sconsiglio fortemente di questo approccio. Supponiamo che il valore di ': a' nella prima mappa sia' [1 2 3] '- il tuo appiattimento lo diffonde in tre valori separati. Questo non è fattibile perché non sei mai sicuro di essere nella "prima" mappa, che deve essere racchiusa in un elenco o in una mappa successiva, che deve essere aggiunta all'elenco esistente. – amalloy

+0

@amalloy: Ho aggiornato il codice per gestire tali condizioni – Ankur

2
(defn value-sets [& maps] 
    (reduce (fn [acc map] 
      (reduce (fn [m [k v]] 
         (update-in m [k] (fnil conj #{}) v)) 
        acc 
        map)) 
      {} maps)) 

Modifica o forse

(defn value-sets [& maps] 
    (reduce (fn [acc [k v]] 
      (update-in acc [k] (fnil conj #{}) v)) 
      {} 
      (apply concat maps))) 

Inoltre modificare, molti anni dopo: avrei scritto in modo diverso ora:

(defn value-sets [maps] 
    (apply merge-with into (for [m maps, [k v] m] 
          {k #{v}}))) 
1

Vedendo la soluzione di amalloy coinvolge reduce suggerito questo per me:

(def maps [{:a 1 :b 2} 
      {:a 11 :b 22 :c 5} 
      {:c 6 :a 7}]) 

(defn my-merge-maps 
    [maps] 
    (reduce (fn [accum [k v]] 
      (if (accum k) 
       (assoc accum k (conj (accum k) v)) 
       (assoc accum k #{v}))) 
      {} 
      (apply concat maps))) 

(defn -main 
    [] 
    (println (my-merge-maps maps))) 

risultato è: {:C#{5 6}, :b #{2 22}, :a #{1 7 11}}

(gfredericks Grazie per aver ricordato che stavo usando per sbaglio varargs. :))

Edit: Ed ecco un altro modo, utilizzando merge-with:

(def maps [{:a 1 :b 2} 
      {:a 11 :b 22 :c 5} 
      {:c 6 :a 7}]) 

(defn build-up-set 
    [curr-val new-val] 
    (if (set? curr-val) 
    (conj curr-val new-val) 
    #{curr-val new-val})) 

(defn my-merge-maps 
    [maps] 
    (apply merge-with build-up-set maps)) 

(defn -main 
    [] 
    (println (my-merge-maps maps))) 
0

Ed ecco un metodo pipeline:

(defn my-merge-maps [stuff] 
    (->> stuff 
     (apply concat) 
     (group-by first) 
     (map (fn [[k vs]] [k (set (map second vs))])) 
     (into {}))) 

Questo potrebbe essere fatto in mappe se avessimo il diritto funzioni di base per manipolarli?

0
(def data [{:a 1 :b "a"} 
      {:a 2 :b "b"} 
      {:a 3 :b "c"} 
      {:a 4 :b "a"}]) 

(apply 
    merge-with 
    clojure.set/union 
    (map #(zipmap (keys %) (map hash-set (vals %))) data)) 
;{:b #{"a" "b" "c"}, :a #{1 2 3 4}} 
0

Amalloy abbandonato la seguente soluzione sullo stesso problema su IRC ieri:

(defn value-sets [& maps]              
    (apply merge-with into (for [m maps, [k v] m] {k #{v}}))) 
Problemi correlati