2009-10-28 4 views
5

Ich bin nicht zu Clojure und versuche herauszufinden, wie das geht.Clojure: Wie wende ich eine Funktion auf eine Teilmenge der Einträge in einer Hash-Map an?

Ich möchte eine neue Hash-Map erstellen, die für eine Teilmenge der Schlüssel in der Hash-Map eine Funktion auf die Elemente anwendet. Was ist der beste Weg, dies zu tun?

(let 
    [my-map {:hello "World" :try "This" :foo "bar"}] 
    (println (doToMap my-map [:hello :foo] (fn [k] (.toUpperCase k))) 

Dies sollte dann eine Karte mit so etwas wie

{:hello "WORLD" :try "This" :foo "BAR"} 

Antwort

24
(defn do-to-map [amap keyseq f] 
    (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq))

Aufteilung:

Es hilft von innen nach außen, es zu betrachten. In Clojure verhalten sich Hash-Maps wie Funktionen; Wenn Sie sie wie eine Funktion mit einem Schlüssel als Argument aufrufen, wird der Wert zurückgegeben, der diesem Schlüssel zugeordnet ist. So einen einzelnen Schlüssel gegeben, kann der aktuelle Wert für diesen Schlüssel werden über erhalten:

(some-map some-key) 

Wir alten Werte nehmen wollen, und sie auf neue Werte ändern, indem Sie eine Funktion f sie auffordern. So einen Schlüssel gegeben, wird der neue Wert sein:

(f (some-map some-key)) 

Wir diesen neuen Wert mit diesem Schlüssel zuordnen mögen in unserer Hash-Karte, den alten Wert „ersetzt“. Dies ist, was assoc tut:

(assoc some-map some-key (f (some-map some-key))) 

(„Ersetzen“ ist in Panik Anführungszeichen, weil wir nicht ein einziges Hash-Map-Objekt mutieren, wir neue, unveränderlich, veränderte Hash-Karte sind wiederkehrende Objekte jedes Mal wir nennen assoc. Diese immer noch schnell und effizient in Clojure ist da Hash-Karten sind persistent und Struktur teilen, wenn Sie assoc sie.)

wir brauchen immer wieder assoc neue Werte auf unserer Karte zu, ein Schlüssel zu einem Zeitpunkt. Wir brauchen also eine Art Schleifenkonstrukt. Wir wollen mit unserer ursprünglichen Hash-Map und einem einzigen Schlüssel beginnen und dann den Wert für diesen Schlüssel "updaten". Dann nehmen wir diese neue Hash-Map und den nächsten Schlüssel und "aktualisieren" den Wert für den nächsten Schlüssel. Und wir wiederholen dies für jeden Schlüssel einzeln und geben schließlich die Hash-Karte zurück, die wir "angesammelt" haben. Dies ist, was reduce tut.

  • Das erste Argument für reduce ist eine Funktion, die zwei Argumente nimmt: ein „Akkumulator“ Wert, der der Wert wir immer halten „Aktualisierung“ ist; und ein einzelnes Argument, das in einer Iteration verwendet wird, um einen Teil des Akkumulierens auszuführen. Das zweite Argument zu reduce ist der Anfangswert, der als erstes Argument an dieses fn übergeben wird.
  • Das dritte Argument zu reduce ist eine Sammlung von Argumenten, die als zweites Argument zu diesem fn nacheinander übergeben werden.

So:

(reduce fn-to-update-values-in-our-map 
     initial-value-of-our-map 
     collection-of-keys) 

fn-to-update-values-in-our-map ist nur die assoc Anweisung von oben, in einer anonymen Funktion eingewickelt:

(fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key)))) 

So ist es in reduce Verstopfen:

(reduce (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key)))) 
     amap 
     keyseq) 

Im Clojure, es gibt eine Abkürzung für das Schreiben anonymer Funktionen: #(...) ist eine anonyme fn bestehend aus einem einzigen Formular, in dem %1 an das erste Argument der anonymen Funktion gebunden ist, %2 an die zweite usw. So kann unsere fn von oben geschrieben werden äquivalent als:

#(assoc %1 %2 (f (%1 %2))) 

Das gibt uns:

(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq) 
+1

Würde es Ihnen etwas ausmachen, eine Aussage nach Aussage Beschreibung für uns neu zu Clojure geben? –

+1

Hoffe, dass hilft. –

+0

Das # (...) Notation ist genial. Gute Antwort! –

1

Das Ergebnis scheint folgende zu arbeiten:

(defn doto-map [ks f amap] 
    (into amap 
    (map (fn [[k v]] [k (f v)]) 
     (filter (fn [[k v]] (ks k)) amap)))) 

user=> (doto-map #{:hello :foo} (fn [k] (.toUpperCase k)) {:hello "World" :try "This" :foo "bar"}) 
{:hello "WORLD", :try "This", :foo "BAR"} 

Es könnte ein besserer Weg, dies zu tun. Vielleicht kann jemand mit einem schönen Einzeiler kommen :)

4
(defn doto-map [m ks f & args] 
    (reduce #(apply update-in %1 [%2] f args) m ks)) 

Beispielaufruf

user=> (doto-map {:a 1 :b 2 :c 3} [:a :c] + 2) 
{:a 3, :b 2, :c 5} 

Hoffnungen das hilft.