【问题标题】:Clojure: iterate over map of setsClojure:迭代集合图
【发布时间】:2015-12-17 17:39:12
【问题描述】:

这几乎是我上一个问题 (Clojure idiomatic way to update multiple values of map) 的后续,但并不完全相同。 (请记住,我对 Clojure 和函数式语言都相当陌生)

假设我有以下数据结构,定义为集合的映射:

(def m1 {:1 #{2} :2 #{1 3} :3 #{1}})

还有这样的地图:

(def m2 {:1 {:1 0 :2 12 :3 23} :2 {:1 23 :2 0 :3 4} :3 {:1 2 :2 4 :3 0}})

我想要做的是将在 m1 中有对应关系的m2 的注册表更新为某个值。假设我想要的值是x。生成的m2 将是这样的:

{:1 {:1 0 :2 x :3 23} :2 {:1 x :2 0 :3 x} :3 {:1 x :2 4 :3 0}}

假设v 包含我的地图的所有可能键,那么你的第一次尝试(我失败得很惨)是做这样的事情:(假设x=1

(for [i v]
 reduce (fn [m j] (assoc-in m [i j] 1)) d (i m1)))

不用说这是一个失败。那么,如何做到这一点的惯用方式呢?

【问题讨论】:

    标签: clojure lisp idioms


    【解决方案1】:

    试试这个(这里 x 是 100)

    (merge-with merge m2 
       (into {} (for [[k v] m1] [k (into {} (for [i v] [(keyword (str i)) 100]))])))
    

    编辑:

    想法是这样的:

    • 将 m1 从 {:1 #{2} :2 #{1 3} :3 #{1}} 转换为 {:1 {:2 x} :2 {:1 x :3 x} :3 {:1 x}},这基本上是将每个集合转换为一个映射,其中键是集合的值,值是常量 x。
    • 合并 m2 和新 m1。

    注意:假设 m1 中的所有键都存在于 m2 中。

    【讨论】:

      【解决方案2】:

      据我了解您的要求

      1. m1 生成多个键序列。
      2. m2 中,将每个键序列与特定的常量值相关联。

      第一步是对m1 进行相当简单的转换。我们希望为每个条目生成 0 个或多个 key-seqs(取决于其集合中有多少个),所以我自然会选择 mapcat。它代表map-then-concat,非常适合从 seq 的每个元素中生成 0 个或更多我们想要的元素的情况。

      (defn key-seqs [coll]
        (mapcat 
         (fn [[k v]] 
           (map (partial vector k) v))
         coll))
      
      (key-seqs m1)
      ;;=> ([:1 2] [:2 1] [:2 3] [:3 1])
      

      注意mapcat 使用的函数本身就是map,因此它会为mapcat 生成一个序列(可能为空)以展开。但这是将存储在集合中的多头作为本身返回。如果我们想把它们变成关键字来匹配m2,我们需要更多的处理:

      (defn key-seqs [coll]
        (mapcat 
         (fn [[k v]] 
           (map (comp (partial vector k) keyword str) v))
         coll))
      
      (key-seqs m1)
      ;;=> ([:1 :2] [:2 :1] [:2 :3] [:3 :1])
      

      (我们需要使用str,因为keyword不知道如何处理long。通常关键字不是数字,而是具有某种象征意义的名称)

      然后我们可以稍微调整您之前问题中的update-m,以便它可以将常量值作为参数并处理不只是两次具有相同值的键序列:

      (defn update-m [m x v]
        (reduce (fn [m' key-seq]
                  (assoc-in m' key-seq x)) ;; accumulate changes
                m   ;; initial-value
                v)) ;; collection to loop over
      

      现在我们似乎在做生意:

      (update-m m2 1 (key-seqs m1))
      ;;=> {:1 {:1 0, :2 1, :3 23}, :2 {:1 1, :2 0, :3 1}, :3 {:1 1, :2 4, :3 0}}
      

      【讨论】:

      • 如果 OP 只使用数字作为开头的键,而不是问题中的这些伪数字关键字,则容易得多。
      • 你先生/女士,太棒了。只有一个问题:您能否进一步解释一下这条线的工作原理:(map (comp (partial vector k) keyword str) v)) 我的意思是,comp 到底在做什么?我知道它正在组成部分函数并将 str 转换为关键字但是你能进一步解释一下吗?
      • @amalloy 如果我这样做了,会更容易吗?我的意思是,这种情况有没有直接的解决方案?
      • 基本过程是mapcat迭代条目[k v],从[:1 #{2}]开始。使用comppartial,它会产生一个类似(fn [num] (vector :1 (keyword (str num)))) 的函数,它映射到集合中的所有数字 v。这给出了一个向量序列,其中所有第一个成员都是:1,然后返回给mapcat。然后将所有这些序列组合成一个序列。学习阅读这些comp&partial 组合可能需要一些时间,因为在返回的函数中,数据首先从最后一个参数 fn 向左流动。
      【解决方案3】:

      我认为一个不错的解决方案是,如果您将 m1 的数据结构更改为类似

      (def m1-new [[:1 :2] [:2 :1] [:2 :3] [:3 :1]])
      

      然后你可以在它上面使用reduce 并使用assoc-in

      (reduce (fn [m path] (assoc-in m path my-new-value)) m2 m1-new)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-04-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-22
        • 2012-07-07
        • 2017-03-10
        相关资源
        最近更新 更多