2012-02-22 21 views
10

En clojure, quiero agregar estos datos:En Clojure, ¿cómo agrupar elementos?

(def data [[:morning :pear][:morning :mango][:evening :mango][:evening :pear]]) 
(group-by first data) 
;{:morning [[:morning :pear][:morning :mango]],:evening [[:evening :mango][:evening :pear]]} 

Mi problema es que :evening y :morning son redundantes. En su lugar, me gustaría crear la colección siguiente:

([:morning (:pear :mango)] [:evening (:mango :pear)]) 

me ocurrió:

(for [[moment moment-fruit-vec] (group-by first data)] [moment (map second moment-fruit-vec)]) 

¿Hay una solución más idiomática?

+1

El nombre de la variable en su la solución propuesta es engañosa. El valor desestructurado como 'fruta' es en realidad una secuencia de vectores de pares momento-fruta. –

+0

¡Muchas gracias! Se actualizó la pregunta – viebel

Respuesta

5

Me he encontrado con problemas de agrupación similares. Por lo general, me acaban de conectar fusionarse con o update-in en alguna etapa de procesamiento siguientes:

(apply merge-with list (map (partial apply hash-map) data)) 

Se obtiene un mapa, pero esto es sólo una siguientes de pares de valores clave:

user> (apply merge-with list (map (partial apply hash-map) data)) 
{:morning (:pear :mango), :evening (:mango :pear)} 
user> (seq *1) 
([:morning (:pear :mango)] [:evening (:mango :pear)]) 

Este la solución solo obtiene lo que desea si cada tecla aparece dos veces, sin embargo. Esto podría ser mejor:

(reduce (fn [map [x y]] (update-in map [x] #(cons y %))) {} data) 

Ambos se sienten "más funcionales" pero también se sienten un poco intrincados. No se deshaga rápidamente de su solución, es fácil de entender y lo suficientemente funcional.

+1

¿Qué piensas de '(aplica merge-with (comp flatten list) (mapa (parcial aplica hash-map) data))'? – viebel

+1

Esa es una solución buena y concisa. Creo que 'flatten' es ** O (n) **, por lo que podría no funcionar bien aplicarlo repetidamente en ciertos conjuntos de datos. –

+1

Tienes razón. Encontré una mejor solución, mira mi respuesta. Por cierto, ¿hay alguna función incorporada que haga lo mismo que 'agg'? – viebel

4

No se apresure a descartar group-by, ha agregado sus datos por la clave deseada y no ha cambiado los datos. Cualquier otra función que espere una secuencia de pares momento-fruta aceptará cualquier valor buscado en el mapa devuelto por group-by.

En términos de cálculo del resumen, mi inclinación era alcanzar merge-with pero para eso tuve que transformar los datos de entrada en una secuencia de mapas y construir un "mapa base" con las claves requeridas y los vectores vacíos como valores .

(let [i-maps (for [[moment fruit] data] {moment fruit}) 
     base-map (into {} 
        (for [key (into #{} (map first data))] 
        [key []]))] 
     (apply merge-with conj base-map i-maps)) 

{:morning [:pear :mango], :evening [:mango :pear]} 
2

Meditar en respuesta @mike t 's, yo he llegado con:

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

Esta solución funciona también cuando las teclas aparecen más de dos veces en data:

(apply merge-with agg (map (partial apply hash-map) 
    [[:morning :pear][:morning :mango][:evening :mango] [:evening :pear] [:evening :kiwi]])) 
;{:morning (:mango :pear), :evening (:kiwi :pear :mango)} 
Cuestiones relacionadas