如何合并地图并获取列表地图?

时间:2016-06-06 23:18:45

标签: clojure functional-programming

假设我们有一张地图清单。地图都有相同的关键字,但我们事先并不知道关键字。

[{:a 1 :b 2} {:a 3 :b 4}]

将这个列表合并到这样的地图中的惯用方法是什么:

{:a [1 3]
 :b [2 4]}

看起来并不难,但是当我开始实现这个功能时,它变得非常丑陋和重复。我有一种感觉,有更清洁的方法来实现这一目标。

谢谢

3 个答案:

答案 0 :(得分:5)

通过使用标准库中的几个函数,您实际上可以获得一个非常优雅的解决方案:

(defn consolidate [& ms]
  (apply merge-with conj (zipmap (mapcat keys ms) (repeat [])) ms))

示例:

(consolidate {:a 1 :b 2} {:a 3 :b 4})
;=> {:a [1 3], :b [2 4]}

这个解决方案的一个很酷的事情是即使地图有不同的密钥集也能正常工作。

答案 1 :(得分:3)

我宁愿使用双重缩减将它们与update“合并”:

(defn merge-maps-with-vec [maps]
  (reduce (partial reduce-kv #(update %1 %2 (fnil conj []) %3))
          {} maps))

user> (merge-maps-with-vec [{:a 1 :b 2} {:a 3 :b 4 :c 10}])
{:a [1 3], :b [2 4], :c [10]}

它不像@Sam Estep的答案那样具有表现力,但另一方面它不会产生任何中间序列(如每个键到空的矢量图,它也需要一个额外的通过每个条目地图)。当然,过早的优化总的来说很糟糕,但我认为这不会受到伤害。虽然基于reduce的解决方案看起来有点模糊,但是如果将其放入具有适当文档的库中,对于最终用户(或一年之后对自己来说)看起来并不模糊。

答案 2 :(得分:0)

虽然可以使用许多解决方案,但这里有一个使用一些便利函数in the Tupelo library

(ns clj.core
  (:use tupelo.core)
  (:require [tupelo.schema :as ts]
            [schema.core :as s] ))

(s/defn gather-keys
  [list-of-maps :- [ts/KeyMap]]
  (newline)
  (let [keys-vec (keys (first list-of-maps))]
    (s/validate [s/Keyword] keys-vec) ; verify it is a vector of keywords
    (apply glue
      (for [curr-key keys-vec]
        {curr-key (forv [curr-map list-of-maps]
                    (get curr-map curr-key))} ))))
(deftest t-maps
  (spyx
    (gather-keys [{:a 1 :b 2}
                  {:a 3 :b 4} ] )))

(gather-keys [{:a 1, :b 2} {:a 3, :b 4}]) ;=> {:a [1 3], :b [2 4]}

请注意,此解决方案假设每个输入映射都有一组相同的键。通常我也希望通过代码中的健全性检查来强制执行该假设。

看看Sam的答案,我会用一些临时变量重写它,以帮助记录子步骤:

(defn consolidate-keys [list-of-maps]
  (let [keys-set     (set (mapcat keys list-of-maps))
        base-result  (zipmap keys-set  (repeat [] )) ]
    (apply merge-with conj base-result list-of-maps)))

(consolidate-keys  [ {:a 1 :b 2}
                     {:a 3 :z 9} ] )
;=> {:z [9], :b [2], :a [1 3]}