2011-10-23 11 views
5

Ciao huys: voglio mappare una "media" per tutti i valori in una mappa. dire che ho un elenco di mappe:Posso "trasporre" un elenco di mappe in una mappa di elenchi in Clojure?

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

E il mio output desiderato è

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

/// seguito sono elencate le divagazioni del mio cervello, vale a dire il modo in cui avrei potuto immaginare questo lavoro in Clojure .. .non essere preso troppo sul serio

recepire l'elenco:

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

e quindi ho potuto semplicemente fare qualcosa come (ancora una volta, che costituiscono una funzione chiamata freduce qui)

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

per ottenere

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

+1

Si intende ridurre in modo da non dover attraversare la sequenza due volte. Inizierai con una mappa vuota come accumulatore e man mano che la tua riduzione arriva in ogni mappa, somma tutti i valori con i valori corrispondenti nell'accumulatore. Sull'ultimo elemento della lista dividere ogni valore per la lunghezza della lista. –

+0

In realtà voglio fare matematica più sofisticata sui dati (deviazione standard, ...), quindi penso di voler disaccoppiare il modo in cui i dati vengono uniti dal modo in cui viene trasposto. – jayunit100

risposta

4

Ecco una soluzione abbastanza prolissa. Speriamo che qualcuno può venire con qualcosa di meglio:

(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 tua è la soluzione corretta, penso, in quanto è molto leggibile. – jayunit100

4

Date un'occhiata a merge-with

Ecco il mio andare a un po 'di codice vero e proprio:

(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

Questo è elegante ma probabilmente un po 'troppo avanzato per me a questo punto. Grazie per avermi indirizzato verso l'unione, lo imparerò ... – jayunit100

6

creare la mappa dei vettori:

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

creare la mappa delle medie:

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

Grazie per la spiegazione ... ha un senso. – jayunit100

1

Ecco un'altra versione che utilizza fusione-con senza 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

Presume che tutte le chiavi siano presenti in tutte le mappe, il che può essere un'ipotesi infondata (non è esplicita, almeno). – mange

+0

hai ragione sull'ipotesi delle chiavi, se alcune mappe mancavano delle chiavi dovresti sostituire il '+' in unione con '# (+ (o% 1 0) (o% 2 0))' – 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

Sembra avere senso. Non sei sicuro di dove si applica la media ... suppongo che mapf>? – jayunit100

+0

Cuore spezzato, lo so? – BLUEPIXY

+0

La riscrittura di tale arbitrariamente è buona se stai dicendo che se il nome della funzione. – BLUEPIXY

1

Ecco la mia soluzione one-liner:

(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} 

Logiche come segue:

  • Usa fusione-con + sulle mappe per calcolare una somma per ogni valore di chiave
  • dividere tutti i valori risultanti per il numero totale di mappe per ottenere una media
  • inserire i risultati di nuovo in un HashMap con (in {} ...)
Problemi correlati