2016-04-26 6 views
2

Diese Funktion nimmt einen Satz von Vektoren von Zahlen und gibt den Schnittpunkt von ihnen:Wie finden Sie die Schnittmenge von Kartensätzen in clojure?

(defn foo [& sets] (apply clojure.set/intersection (map #(set %) sets))) 

zB.

user=> (foo [1 2 3] [3 4 5 1] [33 3 3 1]) 
#{1 3} 

Wie können Sie die gleiche Art von Sache, die Elemente der Eingangssätze gegeben erreichen sind Karten mit einem :id Schlüssel als einzigartige Eigenschaft von der Karte zu benutzen?

dh. Ich versuche, eine Funktion foo2 zu schreiben, das dies tut:

user=> (foo2 [{:id 1 ...} {:id 2 ...} {:id 3 ...}] 
      [{:id 3 ...} {:id 4 ...} {:id 5 ...} {:id 1 ...}] 
      [{:id 33 ...} {:id 3 ...} {:id 3 ...} {:id 1 ...}]) 
[{:id 1 ...} {:id 3 ...}] 

Gibt es eine einfache Möglichkeit, das zu tun?

Oder ist die einzige Möglichkeit, die IDs zu sammeln, den Schnittpunkt der Schlüssel zu bekommen und dann die Schlüssel in den Eingabesätzen zu suchen?

Gibt es eine Möglichkeit, die Mengenfunktionen (Vereinigung, Schnittpunkt usw.) zu "überladen", indem Sie eine Schlüsselwertfunktion definieren, die den Schlüssel für ein beliebiges Objekt zurückgibt, oder nur Sätze mit verwenden können Primitive Werte?

+0

Wie erstellen Sie die resultierende '{: id 1 ...}' Karte aus den gegebenen '{: id 1 ...} Karten? – Thumbnail

+0

@Thumbnail Die Ausgabe-Map sollte dieselbe wie die Eingabe-Map sein; Es ist keine Art von Aggregation der verschiedenen Karten mit einer gemeinsamen ID. In dem Fall, in dem die vollständigen Karten nicht gleich sind (dh mehrere IDs mit unterschiedlichem Inhalt), ist es nicht wichtig, welches ausgewählt ist, solange nur eine Karte die ID in der Ergebnismenge hat. – Doug

Antwort

2

EDIT: Ich zuvor falsch angenommen, Karten mit der gleichen :id sind garantiert, um die gleichen Schlüssel und Werte zu haben. Wenn sie berechtigt sind, anders zu sein, ist dies eine bessere Antwort:

(defn intersect-by [key-fn data] 
    (let [normalized (map (fn [map-data] 
          (->> map-data 
           (group-by key-fn) 
           (map (fn [[key val]] 
             [key (apply merge val)])) 
           (into {}))) 
         data) 
     normalized-keys (map (comp set keys) normalized) 
     intersection (apply clojure.set/intersection normalized-keys) 
     merged-data (apply merge-with merge normalized)] 
    (vals (select-keys merged-data intersection)))) 
#'user/foo 

(def data [[{:id 1} {:id 2} {:id 3, :x 3}] 
      [{:id 3, :y 4} {:id 4} {:id 5} {:id 1}] 
      [{:id 33} {:id 3} {:id 3, :z 5} {:id 1}]]) 
#'user/data 

(intersect-by :id data) 
({:id 1} {:id 3, :x 3, :y 4, :z 5}) 

Wenn Sie zwei Karten mit dem gleichen :id nehmen garantiert die gleichen Schlüssel haben und Werte, Ihre erste foo Funktion tut dies bereits:

(def data [[{:id 1} {:id 2} {:id 3}] 
      [{:id 3} {:id 4} {:id 5} {:id 1}] 
      [{:id 33} {:id 3} {:id 3} {:id 1}]]) 
#'user/data 

(defn foo [& sets] (apply clojure.set/intersection (map #(set %) sets))) 
#'user/foo 

(apply foo data) 
#{{:id 1} {:id 3}} 
+1

die Frage war über verschiedene Karten mit der gleichen ID, ich denke, sonst op hätte es nicht gefragt. – leetwinski

+0

oops, ich werde meine Antwort dann überarbeiten – ProjectFrank

2

ich tun würde, fast das gleiche: clojure.set/intersection, nach jeder Vektor Konvertierung von :id auf nach:

user> (defn intersect [& colls] 
     (apply clojure.set/intersection 
       (map #(apply sorted-set-by 
          (fn [{id1 :id} {id2 :id}] (compare id1 id2)) 
          %) 
        colls))) 
#'user/intersect 

user> (intersect [{:id 1} {:id 2} {:id 3}] 
       [{:id 3} {:id 4} {:id 5} {:id 1}] 
       [{:id 33} {:id 3} {:id 3} {:id 1}]) 
#{{:id 1} {:id 3}} 

Hinweis, dass dieser Ansatz verschiedene Karten mit dem gleichen :id Wert als gleich behandeln würde, aber ich denke, das ist was du brauchst. By the way, sorted-set ist genau die Art und Weise zu "überladen" gesetzt Funktionen, die Sie fragen.

Sie eine Hilfsfunktion machen, um es allgemeinere aussehen:

user> (defn key-comparator [key] 
     #(compare (key %1) (key %2))) 
#'user/key-comparator 

user> (defn intersect [& colls] 
     (apply clojure.set/intersection 
       (map #(apply sorted-set-by (key-comparator :id) %) 
        colls))) 
#'user/intersect 

user> (intersect [{:id 1} {:id 2} {:id 3 :x 1}] 
       [{:id 3 :x 10} {:id 4} {:id 5} {:id 1}] 
       [{:id 33} {:id 3 :x 33} {:id 3 :x 2} {:id 1}]) 
#{{:id 1} {:id 3, :x 33}} 

eine andere Variante alle Elemente aus dem ersten coll, id, deren Filter ist in allen anderen colls:

user> (defn intersect-2 [c & cs] 
     (if (empty? cs) c 
      (filter (fn [{id :id :as itm}] 
         (every? (partial some #(when (= id (:id %)) %)) 
           cs)) 
        c))) 
#'user/intersect-2 

user> (intersect-2 [{:id 1} {:id 2} {:id 3 :x 1}] 
        [{:id 3 :x 10} {:id 4} {:id 5} {:id 1}] 
        [{:id 33} {:id 3 :x 33} {:id 3 :x 2} {:id 1}]) 
({:id 1} {:id 3, :x 1}) 
+0

Wenn Karten mit demselben ': id'-Wert andere Schlüssel und Werte haben dürfen, würden diese anderen Schlüsselwertpaare nicht verloren gehen, wenn sie nicht von der Karte stammen, die" gewonnen "hat? z.B. In Ihrem Beispiel '' {: id 3,: x 33} '' gewinnt '{: id 3: x 2}'. Wenn '{: id 3: x 2}' stattdessen '{: id 3: x 2: y 3}' wäre, würde das '[: y 3 [' Paar im Ergebnis nicht reflektiert. – ProjectFrank

+1

ja, du hast Recht. Es hängt wirklich von der Kollisionslogik in der Aufgabe des Op ab.Da wir wirklich keine zusätzlichen Informationen haben, halte ich alle anderen Schlüssel für unwichtig. Aber wahrscheinlich sollte zuerst gefragt werden) – leetwinski

Verwandte Themen