【问题标题】:Clojure - Updating Each Inner Map in Nested MapClojure - 更新嵌套地图中的每个内部地图
【发布时间】:2017-08-29 20:07:20
【问题描述】:

假设我有一个嵌套的地图结构,比如

{: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)

但我不确定如何将其概括为关​​键字列表。谢谢!

【问题讨论】:

标签: clojure


【解决方案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}}

【讨论】:

    【解决方案2】:

    假设您的嵌套级别是恒定的(根据您的示例,这似乎是一个公平的假设),这是一个可行的答案。请注意,我将键作为一组提供,如果您想使用向量,您可以简单地修改函数以将符号绑定到函数顶部 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))
    

    【讨论】:

      【解决方案3】:

      您可以使用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)))))
      

      【讨论】:

        【解决方案4】:

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

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

        仔细看上面的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)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-07-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-14
          • 1970-01-01
          相关资源
          最近更新 更多