繁体   English   中英

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

[英]In clojure, how to merge several maps combining mappings with same key into a list?

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

但感觉不习惯 还有其他想法吗?

一种方法是

(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) 因此,您可以尝试的另一件事是先转换为向量,然后进行简单的merge-with 由于中间数据结构,这可能比第一种方法的性能稍差。

(defn merge-lists [& maps]
  (apply merge-with into
         (for [m maps, [k v] m]
           {k [v]})))

@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/

与此功能合并:

(defn acc-list [x y]
  (let [xs (if (seq? x) x (cons x nil))]
    (cons y xs)))

使用 group-by 怎么样? 它不会完全返回您要求的内容,但非常相似:

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

或者对 group-by 函数稍作修改:

(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]}

您可以尝试以下方法,我认为它非常有效

(reduce 
  (fn [m pair] (let [[[k v]] (seq pair)]
                 (assoc m k (cons v (m k))))) 
  {} 
  data)

=> {:weather (:sunny), :humor (:happy :sad :happy)}

如果您的集合中有相同数量的密钥:

(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]}

这是一个解决方案,其中每个值都表示为列表,即使是单例:

(->> [{: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]}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM