【问题标题】:In clojure, how to merge several maps combining mappings with same key into a list?在clojure中,如何将多个映射组合具有相同键的映射合并到一个列表中?
【发布时间】:2012-02-23 07:31:53
【问题描述】:

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

但感觉不是惯用的。还有什么想法吗?

【问题讨论】:

  • 最后一个也会导致您的地图值包括集合的问题....

标签: clojure


【解决方案1】:

一种方法是

(defn merge-lists [& maps]
  (reduce (fn [m1 m2]
            (reduce (fn [m [k v]]
                      (update-in m [k] (fnil conj []) v))
                    m1, m2))
          {}
          maps))

这有点难看,但这只是因为您的值尚未列出。它还强制 everything 成为一个列表(所以你会得到: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]})))

【讨论】:

  • 我原以为(apply merge-with (fnil conj []) {} data) 会起作用,但不幸的是它没有。 :(
  • “如果您已经将每个值都作为向量”是什么意思?请写出maps的确切定义。
  • @YehonathanSharvit 如果您的输入映射是{:humor (:happy)} {:humor (:sad) } 而不是{:humor :happy} {:humor :sad},那么merge-with into 将适用于它们。
【解决方案2】:

你可以试试下面的方法,我觉得很有效

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

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

【讨论】:

  • 这只适用于每张地图恰好有一对 k/v 对,这不是我对目标的印象(因为他说的是地图)。
  • 如果你打算这样做,你可以跳过let,改为使用(fn [m [[k v]]] ...)
  • @amalloy 我试过了,但如果没有 let: “nth not supported on this type: PersistentArrayMap”,我就无法让它工作。可能是 1.3 中某处的破坏性错误?
  • 不,你说的很对。我没有考虑或尝试就做出了断言;我忘记了地图不喜欢被解构,所以这种看起来对配对合理的转换不适用于地图。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    与此功能合并:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-01-21
            • 1970-01-01
            • 2011-12-04
            • 2019-01-29
            • 1970-01-01
            相关资源
            最近更新 更多