2016-03-21 10 views
3

Ich denke, diese Frage qualifiziert sich als ein Einstieg Clojure Problem. Ich habe grundsätzlich Probleme, eine Clojure Map mehrfach zu bearbeiten und verschiedene Arten von Daten zu extrahieren.Clojure: Aggregieren und Zählen in Karten

eine Karte wie diese gegeben, ich versuche Einträge auf mehrere verschachtelte Schlüssel basiert zu zählen:

[ 
    { 
    "a": "X", 
    "b": "M", 
    "c": 188 
    }, 
    { 
    "a": "Y", 
    "b": "M", 
    "c": 165 
    }, 
    { 
    "a": "Y", 
    "b": "M", 
    "c": 313 
    }, 
    { 
    "a": "Y", 
    "b": "P", 
    "c": 188 
    } 
] 

Zuerst möchte ich die Einträge von den a-Schlüsselwerte zur Gruppe:

{ 
    "X" : [ 
    { 
     "b": "M", 
     "c": 188 
    } 
    ], 
    "Y" : [ 
    { 
     "b": "M", 
     "c": 165 
    }, 
    { 
     "b": "M", 
     "c": 313 
    }, 
    { 
     "b": "P", 
     "c": 188 
    } 
    ] 
} 

Zweitens möchte ich Werte von b-Tasten als Duplikate zu übernehmen und die verbleibenden Schlüssel ignorieren:

{ 
    "X" : [ 
    { 
     "b": "M" 
    } 
    ], 
    "Y" : [ 
    { 
     "b": "M" 
    } 
    { 
     "b": "P" 
    } 
    ] 
} 

Dann einfach c ount alle Instanzen der b-Taste:

{ 
    "X" : 1, 
    "Y" : 2 
} 

Als ich die Daten durch Monger bekommen, ich definiert:

(defn db-query 
    ([coll-name] 
    (with-open [conn (mg/connect)] 
     (doall (mc/find-maps (mg/get-db conn db-name) coll-name)))) 

und dann schlagen die Straßensperre:

(defn get-sums [request] 
    (->> (db-query "data") 
     (group-by :a) 
     (into {}) 
     keys)) 

Wie Könnte ich von hier weitermachen?

Antwort

4

Dies ist ein naives Konzept, ich bin sicher, es gibt bessere Wege, aber es könnte sein, was Sie brauchen, um es herauszufinden.

(into {} 
    (map  

    ; f  
    (fn [ [k vs] ] ;[k `unique count`] 
     [k (count (into #{} (map #(get % "b") vs)))]) 

    ; coll 
    (group-by #(get % "a") DATA))) ; "a"s as keys 
;user=> {"X" 1, "Y" 2} 

Erläuterung:

; I am using your literal data as DATA, just removed the , and ; 
(def DATA [{... 

(group-by #(get % "a") DATA) ; groups by "a" as keys 
; so I get a map {"X":[{},...] "Y":[{},{},{},...]} 

; then I map over each [k v] pair where 
; k is the map key and 
; vs are the grouped maps in a vector 
(fn [ [k vs] ] 
     ; here `k` is e.g. "Y" and `vs` are the maps {a _, b, _, c _} 

     ; now `(map #(get % "b") vs)` gets me all the b values 
     ; `into set` makes them uniqe 
     ; `count` counts them 
     ; finally I return a vector with the same name `k`, 
     ; but the value is the counted `b`s 
     [k (count (into #{} (map #(get % "b") vs)))]) 

; at the end I just put the result `[ ["Y" 2] ["X" 1] ]` `into` a map {} 
; so you get a map 
+0

Das funktioniert. Wie großartig. Können Sie erklären, wie das 'fn' für den' b'-Teil alle 'b'-Tasten aggregiert? Es ist komisch, weil der erste Schritt die Verwendung von group-by ist, und dann wird es im zweiten Schritt nicht verwendet, aber es muss auch irgendwie gruppiert werden. – frhd

1

Sie können es reduce mit machen:

(def data [{"a" "X", "b" "M", "c" 188} 
      {"a" "Y", "b" "M", "c" 165} 
      {"a" "Y", "b" "M", "c" 313} 
      {"a" "Y", "b" "P", "c" 188}]) 

(def processed (reduce #(update % (%2 "a") (fnil conj #{}) (%2 "b")) 
         {} data)) 

;; {"X" #{"M"}, "Y" #{"M" "P"}} 
;; you create a map of "a" values to a sets of "b" values in one pass 
;; and then you just create a new map with counts 

(reduce-kv #(assoc %1 %2 (count %3)) {} processed) 

;; {"X" 1, "Y" 2} 

so verwendet er die gleiche Logik wie @ birdspider-Lösung, verwendet aber weniger läuft über eine Sammlungen

in einer Funktion:

(defn process [data] 
    (->> data 
     (reduce #(update % (%2 "a") (fnil conj #{}) (%2 "b")) {}) 
     (reduce-kv #(assoc %1 %2 (count %3)) {}))) 

user> (process data) 
;; {"X" 1, "Y" 2} 
2
(def data [{"a" "X", "b" "M", "c" 188} 
     {"a" "Y", "b" "M", "c" 165} 
     {"a" "Y", "b" "M", "c" 313} 
     {"a" "Y", "b" "P", "c" 188}]) 
;; Borrowing data from @leetwinski 

Eine Sache, die Sie wollen vielleicht prüfen, ob Sie die Daten sind definiert, ist Schlüsselwörter anstelle von Strings als Schlüssel zu verwenden. Dies hat den Vorteil, dass Schlüsselwörter als Funktionen verwendet werden können, um auf Dinge in der Karte zuzugreifen, d. H. (get my-map "a") wird (:a my-map).

(defn by-a-key [data] 
    (group-by #(get % "a") data)) 

Ich glaube, Sie können Ihren zweiten Schritt tatsächlich überspringen, wenn es nur benutzt, um zu werden Sie zu Ihrem dritten Schritt zu bekommen, da es nicht notwendig ist, um:

die Daten von „einem“ Schlüssel gruppieren zu erhalten um es zu tun. Beim zweiten Lesen kann ich nicht sagen, ob Sie nur ein Element pro verschiedenem "b" Schlüssel behalten möchten. Ich gehe davon aus, dass Sie nicht festgelegt haben, wie Sie auswählen, was Sie behalten möchten, und sie scheinen sich wesentlich voneinander zu unterscheiden.

(reduce-kv 
    (fn [m k v] 
    (assoc m k 
     (count (filter #(contains? % "b") v)))) 
    {} 
    (by-a-key data)) 

Sie können auch die ganze Sache tun wie so:

(frequencies (map #(get % "a") (filter #(contains? % "b") data))) 

Da Sie filtern kann, enthält „b“ Schlüssel vor Gruppierung Sie auf den Frequenzen Gruppe verlassen können und für Sie zählen.

+1

Vielen Dank für die Erklärung des Stichwortzugriffs. Du hast recht, ich möchte eigentlich Schlüsselwörter verwenden, keine Strings. – frhd

+1

BTW, Haben Sie vor, einen der Y-Datensätze mit dem "b" -Wert von "M" zu löschen? Es war unklar wegen des kleinen Unterschieds zwischen dem Text und den Schnipsel. – BWStearns

+0

Ja, nur eindeutige Werte sollen gezählt werden. Ich denke, Ihre Lösung ist in dieser Hinsicht richtig. – frhd

Verwandte Themen