"join" 两个地图列表是否有 clojure 函数?
Is there a clojure function to "join" two list of maps?
我正在寻找一个类似于 sql 中的连接函数,例如:
这是两个地图列表:
(def a [{:user_id 1 :name "user 1"}
{:user_id 2 :name "user 2"}])
(def b [{:user_id 2 :email "e 2"}
{:user_id 1 :email "e 1"}])
我想在 user_id 加入 a 和 b 以获得:
[{:user_id 1 :name "user 1" :email "e 1"}
{:user_id 2 :name "user 2" :email "e 2"}]
clojure 或其他库中是否有一些函数可以实现此目的?
我认为没有任何简单的函数可以做到这一点,但我可能是错的。
如果你知道每个user_id
存在于每个序列中,那么你可以只对user_id
进行排序,然后将合并应用到相应的映射:
(defn sort-by-user-id
[m]
(sort #(< (:user_id %1) (:user_id %2)) m))
(map merge (sort-by-user-id a) (sort-by-user-id b))
; => ({:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2})
如果您不能假设每个序列中都存在所有相同的 user_id
,我认为您需要做一些稍微复杂的事情才能匹配 user_id
。我假设如果名称映射没有相应的电子邮件映射,您希望保留名称映射不变(反之亦然,缺少名称映射)。如果没有,那么一种选择是去除这些地图并使用上面给出的方法。
这是合并相应名称和电子邮件映射的一种方法。我们可以使用 user_id
作为地图地图中的键来匹配相应的地图。首先创建包含所有以 user_ids 作为键的地图的地图,例如,像这样:
(def az (zipmap (map :user_id a) a)) ; => {2 {:name "user 2", :user_id 2}, 1 {:name "user 1", :user_id 1}}
(def bz (zipmap (map :user_id b) b)) ; => {1 {:email "e 1", :user_id 1}, 2 {:email "e 2", :user_id 2}}
然后像这样合并各个映射,在过程结束时删除键:
(vals (merge-with merge az bz))
; => ({:email "e 2", :name "user 2", :user_id 2} {:email "e 1", :name "user 1", :user_id 1})
综合起来:
(defn map-of-maps
[cm]
(zipmap (map :user_id cm) cm))
(defn merge-maps
[& cms]
(vals
(apply merge-with merge
(map map-of-maps cms))))
让我们确保它适用于缺少的 user_id
s:
(def a+ (conj a {:name "user 3", :user_id 3}))
(def b+ (conj b {:email "e 4", :user_id 4}))
(merge-maps a+ b+)
; => ({:email "e 4", :user_id 4} {:name "user 3", :user_id 3} {:email "e 2", :name "user 2", :user_id 2} {:email "e 1", :name "user 1", :user_id 1})
如果有更简单或更优雅的方法,我不会感到惊讶。这只是我想到的一种策略。
另一个选择,我觉得简单一点:
user=> (map #(apply merge %) (vals (group-by :user_id (concat a b))))
({:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2})
group-by
创建从 :user_id
到包含给定值的所有映射的映射,vals
仅获取值(每个值都是一个向量),最后对于每个向量值,它们被合并。
clojure.set/join 会做的。
(require '[clojure.set :as set])
(set/join a b) ; => #{{:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2}}
在不提供第三个参数的情况下,函数将加入所有公共键:
(def a [{:id1 1 :id2 2 :name "n 1"} {:id1 2 :id2 3 :name "n 2"}])
(def b [{:id1 1 :id2 2 :url "u 1"} {:id1 2 :id2 4 :url "u 2"}])
(def c [{:id1 1 :id2 2 :url "u 1"} {:id1 2 :url "u 2"}]) ; :id2 is missing in 2nd record
(set/join a b) ; #{{:name "n 1", :url "u 1", :id1 1, :id2 2}}
(set/join a c) ; #{{:name "n 2", :url "u 2", :id1 2, :id2 3} {:name "n 1", :url "u 1", :id1 1, :id2 2}}
仅在 id1 上加入 a 和 b:
(set/join a b {:id1 :id1}) ; #{{:name "n 2", :url "u 2", :id1 2, :id2 4} {:name "n 1", :url "u 1", :id1 1, :id2 2}}
我们甚至可以通过来自不同集合的不同键加入:
(set/join a b {:id1 :id2}) ; #{{:name "n 2", :url "u 1", :id1 1, :id2 2}}
我正在寻找一个类似于 sql 中的连接函数,例如:
这是两个地图列表:
(def a [{:user_id 1 :name "user 1"}
{:user_id 2 :name "user 2"}])
(def b [{:user_id 2 :email "e 2"}
{:user_id 1 :email "e 1"}])
我想在 user_id 加入 a 和 b 以获得:
[{:user_id 1 :name "user 1" :email "e 1"}
{:user_id 2 :name "user 2" :email "e 2"}]
clojure 或其他库中是否有一些函数可以实现此目的?
我认为没有任何简单的函数可以做到这一点,但我可能是错的。
如果你知道每个user_id
存在于每个序列中,那么你可以只对user_id
进行排序,然后将合并应用到相应的映射:
(defn sort-by-user-id
[m]
(sort #(< (:user_id %1) (:user_id %2)) m))
(map merge (sort-by-user-id a) (sort-by-user-id b))
; => ({:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2})
如果您不能假设每个序列中都存在所有相同的 user_id
,我认为您需要做一些稍微复杂的事情才能匹配 user_id
。我假设如果名称映射没有相应的电子邮件映射,您希望保留名称映射不变(反之亦然,缺少名称映射)。如果没有,那么一种选择是去除这些地图并使用上面给出的方法。
这是合并相应名称和电子邮件映射的一种方法。我们可以使用 user_id
作为地图地图中的键来匹配相应的地图。首先创建包含所有以 user_ids 作为键的地图的地图,例如,像这样:
(def az (zipmap (map :user_id a) a)) ; => {2 {:name "user 2", :user_id 2}, 1 {:name "user 1", :user_id 1}}
(def bz (zipmap (map :user_id b) b)) ; => {1 {:email "e 1", :user_id 1}, 2 {:email "e 2", :user_id 2}}
然后像这样合并各个映射,在过程结束时删除键:
(vals (merge-with merge az bz))
; => ({:email "e 2", :name "user 2", :user_id 2} {:email "e 1", :name "user 1", :user_id 1})
综合起来:
(defn map-of-maps
[cm]
(zipmap (map :user_id cm) cm))
(defn merge-maps
[& cms]
(vals
(apply merge-with merge
(map map-of-maps cms))))
让我们确保它适用于缺少的 user_id
s:
(def a+ (conj a {:name "user 3", :user_id 3}))
(def b+ (conj b {:email "e 4", :user_id 4}))
(merge-maps a+ b+)
; => ({:email "e 4", :user_id 4} {:name "user 3", :user_id 3} {:email "e 2", :name "user 2", :user_id 2} {:email "e 1", :name "user 1", :user_id 1})
如果有更简单或更优雅的方法,我不会感到惊讶。这只是我想到的一种策略。
另一个选择,我觉得简单一点:
user=> (map #(apply merge %) (vals (group-by :user_id (concat a b))))
({:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2})
group-by
创建从 :user_id
到包含给定值的所有映射的映射,vals
仅获取值(每个值都是一个向量),最后对于每个向量值,它们被合并。
clojure.set/join 会做的。
(require '[clojure.set :as set])
(set/join a b) ; => #{{:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2}}
在不提供第三个参数的情况下,函数将加入所有公共键:
(def a [{:id1 1 :id2 2 :name "n 1"} {:id1 2 :id2 3 :name "n 2"}])
(def b [{:id1 1 :id2 2 :url "u 1"} {:id1 2 :id2 4 :url "u 2"}])
(def c [{:id1 1 :id2 2 :url "u 1"} {:id1 2 :url "u 2"}]) ; :id2 is missing in 2nd record
(set/join a b) ; #{{:name "n 1", :url "u 1", :id1 1, :id2 2}}
(set/join a c) ; #{{:name "n 2", :url "u 2", :id1 2, :id2 3} {:name "n 1", :url "u 1", :id1 1, :id2 2}}
仅在 id1 上加入 a 和 b:
(set/join a b {:id1 :id1}) ; #{{:name "n 2", :url "u 2", :id1 2, :id2 4} {:name "n 1", :url "u 1", :id1 1, :id2 2}}
我们甚至可以通过来自不同集合的不同键加入:
(set/join a b {:id1 :id2}) ; #{{:name "n 2", :url "u 1", :id1 1, :id2 2}}