Clojure - 在嵌套映射中更新每个内部映射

时间:2017-08-29 20:07:21

标签: 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)

但我不确定如何将其推广到关键字列表。谢谢!

4 个答案:

答案 0 :(得分:1)

在Clojure Cookbook的脚步中,我定义了

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

答案 1 :(得分: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))

答案 2 :(得分:0)

您首先可以定义一个在详细级别工作的函数:

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

make-changes

仔细查看上面的(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) 来回答这个问题:

line = "1 34 1/10/40G IPS,2/4/8/10/16G FC Module DS-X9334-K9 ok"
print " ".join(line.split()[2:-2])

答案 3 :(得分: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)))))