2012-02-23 14 views
19

En Clojure, me gustaría combinar varios mapas en un solo mapa donde las asignaciones con la misma clave se combinan en una lista.En clojure, ¿cómo combinar varios mapas que combinan mapeos con la misma clave en una lista?

Por ejemplo:

{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny} 

debería conducir a:

{:weather :sunny, :humor (:happy :sad :happy)} 

pensé:

(merge-with (comp flatten list) data) 

pero no es eficiente porque aplanar tiene O (n) complejidad.

Entonces se me ocurrió:

(defn agg[x y] (if (coll? x) (cons y x) (list y x))) 
(merge-with agg data) 

Pero no se siente idiomática. ¿Alguna otra idea?

+0

el último también causa problemas en los valores del mapa, incluidas las colecciones ... – mikera

Respuesta

12

Un enfoque sería

(defn merge-lists [& maps] 
    (reduce (fn [m1 m2] 
      (reduce (fn [m [k v]] 
         (update-in m [k] (fnil conj []) v)) 
        m1, m2)) 
      {} 
      maps)) 

Es un poco feo, pero eso es sólo porque sus valores no son ya listas. También fuerza todo para ser una lista (por lo que obtendría :weather [:sunny] en lugar de :weather :sunny). Francamente, es probable que sea un millón de veces más fácil para ti trabajar de todos modos.

Si ya tiene cada valor como vector, simplemente puede hacer (apply merge-with into maps).

+0

Hubiera esperado que '(aplicar merge-with (fnil conj []) {} datos)' funcionaría, pero desafortunadamente no. :( – kotarak

+0

¿A qué te refieres con 'Si ya tenías cada valor como vector'? Escribe la definición exacta de 'maps'. – viebel

+1

@YehonathanSharvit Si tus mapas de entrada son '{: humor (: feliz)} {: humor (: triste)} 'en lugar de' {: humor: feliz} {: humor: triste} 'entonces' merge-with into' funcionaría en ellos. –

1

Usted podría intentar lo siguiente, creo que es bastante eficiente

(reduce 
    (fn [m pair] (let [[[k v]] (seq pair)] 
       (assoc m k (cons v (m k))))) 
    {} 
    data) 

=> {:weather (:sunny), :humor (:happy :sad :happy)} 
+0

Esto solo funciona si cada mapa tiene exactamente un par de k/v, lo cual no fue la impresión que obtuve del objetivo (ya que dijo los mapas). – amalloy

+0

Sin embargo, si vas a hacerlo de esta manera, puedes omitir 'let', al hacer' (fn [m [[k v]]] ...) '. – amalloy

+1

@amalloy Lo intenté pero no pude hacerlo funcionar sin el permiso: "nth no compatible con este tipo: PersistentArrayMap". Podría ser un error de desestructuración en algún lugar de 1.3? – mikera

0

He aquí una solución en la que cada valor está representado como listas, aunque sean hijos únicos:

(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}] 
    (map first) 
    (reduce (fn [m [k v]] (update-in m [k] #(cons v %))) {})) 

=> {:weather (:sunny), :humor (:happy :sad :happy)} 

Si no desea envolver únicos en una Enumere entonces pensé que su solución original estaba bien. La única forma de hacerlo más idiomático es usar core.match.

(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}] 
    (apply merge-with #(match %1 
           [& _] (conj %1 %2) 
           :else [%1 %2]))) 

=> {:weather :sunny, :humor [:happy :sad :happy]} 
1

fusionarse con esta función:

(defn acc-list [x y] 
    (let [xs (if (seq? x) x (cons x nil))] 
    (cons y xs))) 
1

Qué acerca del uso del grupo por?No devuelve exactamente lo que pide, pero es muy similar:

user=> (group-by first (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot})) 
{:humor [[:humor :happy] [:humor :sad] [:humor :happy] [:humor :whooot]], :weather [[:weather :sunny]]} 

o con una pequeña modificación en la función de grupo por:

(defn group-by-v2 
[f vf coll] 
    (persistent! 
    (reduce 
    (fn [ret x] 
     (let [k (f x)] 
     (assoc! ret k (conj (get ret k []) (vf x))))) 
    (transient {}) coll))) 

se convierte en:

user=> (group-by-v2 key val (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot})) 
{:humor [:happy :sad :happy :whooot], :weather [:sunny]} 
Cuestiones relacionadas