Clojure: iterate over map of sets



这几乎是我上一个问题的后续(Clojure惯用的方式来更新map的多个值),但不完全相同。(请记住,我对Clojure和函数式语言都相当陌生)

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

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

和map的map:

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

不用说,那是一次失败。那么,怎么用习惯的方式来做呢?

据我所知,你们想要

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

第一步是对m1进行相当简单的转换。我们希望为每个条目生成0或更多的键序列(取决于它的集合中有多少个键),因此对我来说,自然的选择是mapcat。它代表map -然后- concat,对于从序列的每个元素中产生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展开。但是这里返回的是存储在集合中的long类型本身。如果我们想把它们转换成关键字来匹配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不知道如何处理长整数。关键字通常不是数字,而是具有某种符号意义的名称)

那么我们可以稍微调整一下你上一个问题中的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}}

我认为一个很好的解决方案是,如果你把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)

试试这个(这里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中

最新更新