Clojure 如何合并地图并获得列表地图?

Clojure 如何合并地图并获得列表地图?,clojure,functional-programming,Clojure,Functional Programming,假设我们有一个地图列表。所有地图都有相同的关键字,但我们事先不知道这些关键字 [{:a 1 :b 2} {:a 3 :b 4}] 将这个列表合并成这样一张地图的惯用方法是什么: {:a [1 3] :b [2 4]} 看起来并不难,但是当我开始实现这个函数时,它变得非常丑陋和重复。我有一种感觉,有很多更干净的方法来实现这一点 谢谢虽然有很多解决方案,但这里有一个使用了一些方便功能的解决方案: 请注意,此解决方案假定每个输入映射都有一组相同的键。通常情况下,我希望通过在代码中进行健全性检查来

假设我们有一个地图列表。所有地图都有相同的关键字,但我们事先不知道这些关键字

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

虽然有许多解决方案是可能的,但这里有一个解决方案使用了一些方便的功能:

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

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

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

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

此解决方案的一个很酷的地方是,即使地图具有不同的键集,它也可以工作。

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

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

这个解决方案的一个很酷的地方是,即使地图有不同的键集,它也能工作。

我更愿意使用双重还原将它们与
更新“合并”:

(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
的解决方案看起来有点模糊,但对于最终用户(或一年后的您自己)来说,将其放入带有适当文档的库中不会显得模糊

我宁愿使用双重简化将其与
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
的解决方案看起来有点晦涩难懂,但对于最终用户(或一年后的你自己)来说,如果将其放入带有适当文档的库中,它就不会显得那么晦涩难懂了。

这是一个非常简短的解决方案,但直到我尝试了REPL中的每一小段内容,我才理解它
mapcat
在那里非常有用。尽管参数类型不同:[ms]与[&ms],但看起来很不错。调用consolidate时,是否有办法将ms列表作为参数进行扩展?Oguz-请参阅我对Sam解决方案的重写。@OguzBilgic是;您可以将参数向量更改为just
[ms]
或使用:
(应用合并[{:a1:b2}{:a3:b4}])
这是一个非常简短的解决方案,但直到我尝试了REPL中的每个小部分,我才理解它
mapcat
在那里非常有用。尽管参数类型不同:[ms]与[&ms],但看起来很不错。调用consolidate时,是否有办法将ms列表作为参数进行扩展?Oguz-请参阅我对Sam解决方案的重写。@OguzBilgic是;您可以将参数向量更改为仅
[ms]
或使用:
(应用合并[{:a1:b2}{:a3:b4}])
我已经接受了他的答案,但感谢您的重写,更容易理解。为什么在每个答案中都包含
tupelo
库?这只会让任何人更难真正使用你的代码,而且通常不会产生更好的解决方案。我不想我的解决方案是一个“诡计答案”。。。山姆:我很少看到使用
mapcat
zipmap
,所以我总是不得不停下来,弄清楚到底发生了什么,尤其是在这种情况下,因为
mapcat
生成了许多重复的键,这些键会自动消除重复。在我在repl.amalloy中原型化它之前,这是一个相当令人头痛的问题:对于新用户来说,原生clojure中的许多函数都很容易出错,并且经常容易出错。许多人也有“模糊”和非直觉行为(参见示例)。Tupelo库旨在使Clojure更简单、更可预测、更防弹,特别是对于新用户。每次我向新用户展示它时,他们都会为core Clojure中不存在这些函数而大吃一惊。我已经接受了他的答案,但感谢您的重写,更容易理解。为什么在每个答案中都包含您的
tupelo
库?这只会让任何人更难真正使用你的代码,而且通常不会产生更好的解决方案。我不想我的解决方案是一个“诡计答案”。。。山姆:我很少看到使用
mapcat
zipmap
,所以我总是不得不停下来,弄清楚到底发生了什么,尤其是在这种情况下,因为
mapcat
生成了许多重复的键,这些键会自动消除重复。在我在repl.amalloy中原型化它之前,这是一个相当令人头痛的问题:对于新用户来说,原生clojure中的许多函数都很容易出错,并且经常容易出错。许多人也有“模糊”和非直觉行为(参见示例)。Tupelo库旨在使Clojure更简单、更可预测、更防弹,特别是对于新用户。每次我向新用户展示它时,他们都会为这些功能不可用而大吃一惊