2017-08-29 71 views
1

說,我有一個嵌套的地圖結構,如的Clojure - 更新每個內部地圖中嵌套地圖

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

這個例子只有兩個值,但總體上可能會有更多。我知道每個嵌套地圖(上例中:m1,:m2和:m3)的鍵都是相同的。我有一個關鍵字列表,說

[:m1 :m3] 

,我想除以一定數目的每個內部地圖的價值,說5,爲每個列表中給出的關鍵詞。繼續我的例子,我想得到

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

我該怎麼做?對於一個固定的內部密鑰,如:m1,我可以做

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

但我不知道如何推廣到關鍵字列表。謝謝!

+1

看看[幽靈](https://github.com/nathanmarz /幽靈)。 –

回答

0

這是一個可行的答案,假設你的嵌套層次是恆定的(根據你的例子,這似乎是一個公平的假設)。請注意,我將密鑰提供爲一個集合,如果要使用矢量,只需修改該函數即可將函數的頂部的let中的符號綁定到哈希集合調用。

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

或者,如果你希望只在一個按鍵,在經過一段時間:

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

經過一番思考,在上面的例子中並沒有跟我坐好。並不是他們錯了,只是感覺過於設計。我認爲下面的內容更接近你的想法,並且簡潔得多。你可以明顯地將硬編碼的/ 2變成一個函數來概括這個。

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

你可以首先是定義在細節層面工作的功能:

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

正如你可以看到一些地圖項值將被改造,而其他人將被留下,因爲他們是。

你的榜樣輸入數據:

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

所以在這裏我們來看看在外部結構,以應用細節函數f

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

當然f中會apply-effect

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

仔細看看上述函數中使用這個抽象兩次:

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

因此,我們可以使用map-map-entries現在回答這個問題:

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

您可以使用specter做這樣的轉換:

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

在Clojure食譜的腳步,我會定義

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

...然後使用核心功能做你想做什麼:

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

例如,

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