在clojure中,如何合并将映射与相同键组合在一起的多个映射到列表中?

时间:2012-02-23 07:31:53

标签: clojure

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

但感觉不是惯用。还有其他想法吗?

7 个答案:

答案 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]}