Clojure:迭代集合图
Clojure: iterate over map of sets
这几乎是我上一个问题 () 的后续问题,但并不完全相同。 (请记住,我对 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}})
我想做的是更新 m2
中与 m1 对应的注册表到某个值。假设我想要的值是 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)))
不用说它是失败的。那么,惯用的方法是如何做到这一点?
试试这个(这里 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 中。
据我了解您的要求
- 从
m1
生成一些键序列。
- 在
m2
中将每个键序列与特定的常量值相关联。
第一步是 m1
的相当简单的转换。我们想为每个条目生成 0 个或更多键序列 (取决于其集合中有多少),因此对我来说自然的选择是 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
,所以它为[=17=产生了一个序列(可能为空) ]展开。但这是将存储在集合中的多头作为它们自己返回。如果我们想把它们变成关键字来匹配 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}}
我认为一个不错的解决方案是,如果您将 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)
这几乎是我上一个问题 (
假设我有以下数据结构,定义为集合映射:
(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}})
我想做的是更新 m2
中与 m1 对应的注册表到某个值。假设我想要的值是 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)))
不用说它是失败的。那么,惯用的方法是如何做到这一点?
试试这个(这里 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 中。
据我了解您的要求
- 从
m1
生成一些键序列。 - 在
m2
中将每个键序列与特定的常量值相关联。
第一步是 m1
的相当简单的转换。我们想为每个条目生成 0 个或更多键序列 (取决于其集合中有多少),因此对我来说自然的选择是 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
,所以它为[=17=产生了一个序列(可能为空) ]展开。但这是将存储在集合中的多头作为它们自己返回。如果我们想把它们变成关键字来匹配 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}}
我认为一个不错的解决方案是,如果您将 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)