2011-10-23 12 views
5

Hola, amigos: Quiero mapear un "promedio" para todos los valores en un mapa. decir que tengo una lista de mapas:¿Puedo "transponer" una lista de mapas en un mapa de listas en Clojure?

[{"age" 2 "height" 1 "weight" 10}, 
{"age" 4 "height" 4 "weight" 20}, 
{"age" 7 "height" 11 "weight" 40}] 

Y mi salida deseada es

{"age 5 "height" 5 ....} 

/// A continuación se presentan las divagaciones de mi cerebro, es decir, la forma en que podría imaginar que este trabajo en Clojure .. .no ser tomado demasiado en serio

transponer la lista:

{"age" [2 4 7] "height" [1 4 11] } 

y luego yo podría simplemente hacer algo al igual que (una vez más, lo que representa una función llamada freduce aquí)

(freduce average (vals (map key-join list))) 

para obtener

{"age" 5 "weight" 10 "height" 7}

+1

Va a querer reducir para que no tenga que recorrer la secuencia dos veces. Comenzará con un mapa vacío como su acumulador y, a medida que su reducción llegue a cada mapa, sume todos los valores con los valores correspondientes en el acumulador. En el último elemento de la lista, divida cada valor por la longitud de la lista. –

+0

Realmente quiero hacer matemáticas más sofisticadas en los datos (desviación estándar, ...), así que estoy pensando que quiero desacoplar la forma en que se fusionan los datos de la forma en que se transpone. – jayunit100

Respuesta

4

Aquí hay una solución bastante detallada. Esperemos que alguien puede llegar a algo mejor:

(let [maps [{"age" 2 "height" 1 "weight" 10}, 
      {"age" 4 "height" 4 "weight" 20}, 
      {"age" 7 "height" 11 "weight" 40}] 
     ks (keys (first maps)) 
     series-size (count maps) 
     avgs (for [k ks] 
      (/ (reduce + 
         (for [m maps] 
          (get m k))) 
       series-size))] 
    (zipmap ks avgs)) 
+0

la suya es la solución correcta, creo, en el que es muy fácil de leer. – jayunit100

4

Tome un vistazo a merge-with

Aquí está mi ir en algún código real:

(let [maps [{"age" 2 "height" 1 "weight" 10}, 
      {"age" 4 "height" 4 "weight" 20}, 
      {"age" 7 "height" 11 "weight" 40}]] 
    (->> (apply merge-with #(conj %1 %2) 
      (zipmap (apply clojure.set/union (map keys maps)) 
        (repeat [])) ; set the accumulator 
      maps) 
     (map (fn [[k v]] [k (/ (reduce + v) (count v))])) 
     (into {}))) 
+1

Esto es elegante, pero probablemente un poco demasiado avanzado para mí en este punto. Gracias por dirigirme hacia la fusión, con lo aprenderé ... – jayunit100

6

crear el mapa de vectores:

 
(reduce (fn [m [k v]] 
      (assoc m k (conj (get m k []) v))) 
     {} 
     (apply concat list-of-maps)) 

crear el mapa de los promedios:

 
(reduce (fn [m [k v]] 
      (assoc m k (/ (reduce + v) (count v)))) 
     {} 
     map-of-vectors) 
+0

Gracias por la explicación ... tiene sentido. – jayunit100

1

Aquí hay otra versión que utiliza fusionar-con sin zipmap.

(let [data [{:a 1 :b 2} {:a 2 :b 4} {:a 4 :b 8}] 
      num-vals (count data)] 
    (->> data (apply merge-with +) 
      (reduce (fn [m [k v]] (assoc m k (/ v num-vals))) {}))) 
+1

Esto supone que todas las teclas están presentes en todos los mapas, que pueden ser una suposición infundada (que no es explícita, al menos). – mange

+0

que tienes razón en el supuesto de teclas, si algunos mapas faltaban teclas que se necesitaría para reemplazar el '' + en combinación con-con '# (+ (o 1% 0) (o 2% 0))' Parece – user499049

2
(defn key-join [map-list] 
    (let [keys (keys (first map-list))] 
     (into {} (for [k keys] [k (map #(% k) map-list)])))) 
(defn mapf [f map] 
    (into {} (for [[k v] map ] [k (f v)]))) 
(defn average [col] 
    (let [n (count col) 
     sum (apply + col)] 
     (/ sum n))) 

DEMO

user=> (def data-list [{"age" 2 "height" 1 "weight" 10}, 
{"age" 4 "height" 4 "weight" 20}, 
{"age" 7 "height" 11 "weight" 40}]) 
#'user/data-list 
user=> (key-join data-list) 
{"age" (2 4 7), "height" (1 4 11), "weight" (10 20 40)} 
user=> (mapf average (key-join data-list)) 
{"age" 13/3, "height" 16/3, "weight" 70/3} 
+0

tener sentido. No estoy seguro de dónde se aplica el promedio ... mapf supongo>? – jayunit100

+0

Heartbroken, ¿De verdad lo sabes? – BLUEPIXY

+0

La reescritura de tal arbitrariamente es buena si lo dices si el nombre de la función. – BLUEPIXY

1

es mi solución de una sola línea aquí:

(def d [{"age" 2 "height" 1 "weight" 10}, 
    {"age" 4 "height" 4 "weight" 20}, 
    {"age" 7 "height" 11 "weight" 40}]) 

(into {} (map (fn [[k v] [k (/ v (count d))]]) (apply merge-with + d))) 

=> {"height" 16/3, "weight" 70/3, "age" 13/3} 

Lógicas de la siguiente manera:

  • Uso fusionar-con + en los mapas para calcular una suma para cada valor de la clave
  • dividir todos los valores resultantes por el número total de mapas para obtener un promedio
  • puso a los resultados de vuelta en un HashMap con (en {} ...)
Cuestiones relacionadas