2017-08-29 1 views
1

Say I eine verschachtelte Map-Struktur haben, wieClojure - Aktualisierung jeder innere Karte in Nested Karte

{:val1 {:m1 1 :m2 2 :m3 2} :val2 {:m1 4 :m2 8 :m3 7}} 

nur Dieses Beispiel hat zwei Werte, aber es im Allgemeinen könnte mehr sein. Ich weiß, dass die Schlüssel für jede der verschachtelten Karten gleich sind (: m1,: m2 und: m3 im obigen Beispiel). Ich habe eine Liste von Schlüsselwörtern, sagen

[:m1 :m3] 

, und ich möchte den Wert jeder inneren Karte durch eine Zahl teilen, sagen 5, für jedes der Schlüsselwörter in der Liste angegeben. Fortfahrend mit meinem Beispiel möchte ich erhalten

{:val1 {:m1 1/5 :m2 2 :m3 2/5} :val2 {:m1 4/5 :m2 8 :m3 7/5}} 

Wie kann ich das tun? Für einen festen inneren Schlüssel wie: m1, kann ich

(map #(update-in % [1 :m1]/5) nested-map) 

tun, aber ich bin nicht sicher, wie dies von Schlüsselwort, um eine Liste zu verallgemeinern. Vielen Dank!

+1

Werfen Sie einen Blick auf [Gespenst] (https://github.com/nathanmarz /Gespenst). –

Antwort

0

Hier ist eine Antwort, die funktioniert, vorausgesetzt, dass Ihre Verschachtelungsebene konstant ist (was für Ihr Beispiel scheint eine faire Annahme). Beachten Sie, dass ich die Schlüssel als Set zur Verfügung stelle. Wenn Sie einen Vektor verwenden möchten, können Sie einfach die Funktion so ändern, dass ein Symbol an einen Hash-Set-Aufruf in einem Let am oberen Rand der Funktion gebunden wird.

(defn change-map [m f ks] 
    (for [[k1 v1] m] 
     (hash-map k1 (into {} (for [[k2 v2] v1] 
            (if (contains? ks k2) [k2 (f v2)] 
             [k2 v2])))))) 

(change-map example #{:m1 :m3}) 

Oder wenn Sie nur in Schlüssel eine nach der anderen passieren vorziehen würden:

(defn change-map [m f & ks] 
    (let [ks (apply hash-set ks)] 
     (for [[k1 v1] m] 
      (hash-map k1 (into {} (for [[k2 v2] v1] 
             (if (contains? ks k2) [k2 (f v2)] 
              [k2 v2]))))))) 

(change-map example #(/ % 2) :m1 :m3) 

Nach einigem Nachdenken der obigen Beispiele mit mir nicht gut sitzen. Nicht, dass sie falsch lagen, sie fühlten sich einfach zu überentwickelt. Ich denke, das Folgende ist näher an dem, was Sie im Sinn hatten, und ist wesentlich prägnanter. Sie können dies natürlich verallgemeinern, indem Sie die fest codierte / 2 als eine Funktion ändern.

(into {} (map (fn[[k v]] {k (reduce #(update %1 %2/2) v [:m1 :m3])}) example)) 
0

Sie könnten zunächst einmal definieren eine Funktion, die auf der Detailebene funktioniert:

(let [interesting-keys (set [:m1 :m3])] 
    (defn apply-effect [[k v]] 
    (if (interesting-keys k) 
     [k (/ v 5)] 
     [k v]))) 

Wie Sie einige MapEintrag Werte umgewandelt werden sehen können, während andere so bleiben, wie sie waren.

Ihr Beispiel Eingangsdaten:

(def data {:val1 {:m1 1 :m2 2 :m3 2} :val2 {:m1 4 :m2 8 :m3 7}}) 

hier also wir über die äußere Struktur zu suchen, um eine Detailfunktion f anwenden:

: f wird apply-effect sein

(defn make-changes [m f] 
    (->> m 
     (map (fn [[k v]] 
       [k (->> v 
         (map f) 
         (into {}))])) 
     (into {}))) 

Natürlich

(make-changes data apply-effect) 
;;=> {:val1 {:m1 1/5, :m2 2, :m3 2/5}, :val2 {:m1 4/5, :m2 8, :m3 7/5}} 

Schauen Sie genau, um daszu sehenFunktion über diese Abstraktion zweimal:

(defn map-map-entries [f m] 
    (->> m 
     (map f) 
     (into {}))) 

So können wir nun die Frage map-map-entries mit Antwort:

(map-map-entries 
    (fn [[k v]] 
    [k (map-map-entries 
     apply-effect 
     v)]) 
    data) 
0

Sie specter diese Transformation zu tun, verwenden können:

(:require [clojure.test :refer :all] 
      [com.rpl.specter :as specter]) 

(deftest ^:focused transform-test 
    (let [selectors #{:m1 :m3} 
     input {:val1 {:m1 1 :m2 2 :m3 2} :val2 {:m1 4 :m2 8 :m3 7}} 
     output {:val1 {:m1 1/5 :m2 2 :m3 2/5} :val2 {:m1 4/5 :m2 8 :m3 7/5}}] 

     (is (= output 
       (specter/transform [specter/MAP-VALS 
            specter/ALL 
            #(contains? selectors (first %)) 
            specter/LAST] 
            #(/ % 5) 
            input))))) 
0

In der Spuren des Clojure Kochbuchs würde ich definieren

(defn map-vals [f m] 
    (zipmap (keys m) (map f (vals m)))) 

... dann Kernfunktionen nutzen zu tun, was Sie wollen:

(defn map-inner-keys-with [ks f m] 
    (map-vals 
    (fn [vm] (into vm (map (juxt identity (comp f vm)) ks))) 
    m)) 

Zum Beispiel

(map-inner-keys-with [:m1 :m3] #(/ % 5) 
        {:val1 {:m1 1 :m2 2 :m3 2} 
         :val2 {:m1 4 :m2 8 :m3 7}}) 
=> {:val1 {:m1 1/5, :m2 2, :m3 2/5}, :val2 {:m1 4/5, :m2 8, :m3 7/5}}