在Clojure中,我想将几个地图组合成一个地图,其中具有相同键的映射被组合成一个列表。
例如:
{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}
应该导致:
{:weather :sunny, :humor (:happy :sad :happy)}
我想到了:
(merge-with (comp flatten list) data)
但它效率不高,因为flatten具有 O(n)复杂度。
然后我想出了:
(defn agg[x y] (if (coll? x) (cons y x) (list y x)))
(merge-with agg data)
但感觉不是惯用。还有其他想法吗?
答案 0 :(得分:12)
一种方法是
(defn merge-lists [& maps]
(reduce (fn [m1 m2]
(reduce (fn [m [k v]]
(update-in m [k] (fnil conj []) v))
m1, m2))
{}
maps))
这有点难看,但这只是因为你的价值不是列表。它还强制所有成为一个列表(因此你得到:weather [:sunny]
而不是:weather :sunny
)。坦率地说,无论如何,这对你来说可能要容易一百万次。
如果您已将每个值都作为向量,则可以执行(apply merge-with into maps)
。
答案 1 :(得分:2)
@ amalloy的回答可以通过使用for
来平缓一点。
(reduce (fn [m [k v]] (update-in m [k] (fnil conj []) v)) {} (for [m data entry m] entry))
此技术的来源:http://clj-me.cgrand.net/2010/01/19/clojure-refactoring-flattening-reduces/
答案 2 :(得分:1)
您可以尝试以下方法,我认为这非常有效
(reduce
(fn [m pair] (let [[[k v]] (seq pair)]
(assoc m k (cons v (m k)))))
{}
data)
=> {:weather (:sunny), :humor (:happy :sad :happy)}
答案 3 :(得分:1)
合并此功能:
(defn acc-list [x y]
(let [xs (if (seq? x) x (cons x nil))]
(cons y xs)))
答案 4 :(得分:1)
如何使用分组?它并不完全符合您的要求,但它非常相似:
user=> (group-by first (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot}))
{:humor [[:humor :happy] [:humor :sad] [:humor :happy] [:humor :whooot]], :weather [[:weather :sunny]]}
或者对分组功能进行少量修改:
(defn group-by-v2
[f vf coll]
(persistent!
(reduce
(fn [ret x]
(let [k (f x)]
(assoc! ret k (conj (get ret k []) (vf x)))))
(transient {}) coll)))
变为:
user=> (group-by-v2 key val (concat {:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny :humor :whooot}))
{:humor [:happy :sad :happy :whooot], :weather [:sunny]}
答案 5 :(得分:0)
这是一个解决方案,其中每个值都表示为列表,即使是单身人士:
(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}]
(map first)
(reduce (fn [m [k v]] (update-in m [k] #(cons v %))) {}))
=> {:weather (:sunny), :humor (:happy :sad :happy)}
如果你不想将单身人士列入名单,那么我认为你原来的解决方案就好了。使其更具惯用性的唯一方法是使用core.match。
(->> [{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}]
(apply merge-with #(match %1
[& _] (conj %1 %2)
:else [%1 %2])))
=> {:weather :sunny, :humor [:happy :sad :happy]}
答案 6 :(得分:0)
如果您的集合中有相同数量的密钥:
(defn fn-format [maps]
(reduce (fn [r k] ; result = {}, keys = [:humor, :weather]
(assoc r k (mapv #(get % k) maps)))
{}
[:humor, :weather]))
(fn-format [{:humor :happy :weather :cloudy}
{:humor :sad :weather :sunny}
{:humor :happy :weather :rainy}
{:weather :sunny :humor :happy}])
=> {:humor [:happy :sad :happy :happy], :weather [:cloudy :sunny :rainy :sunny]}