Is there a clojure function to "concatenate" two lists of cards?

I am looking for a join function that is similar to join in sql, for example:

Here are two lists of cards:

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

      

I want to join a and b on user_id to get:

[{:user_id 1 :name "user 1" :email "e 1"} 
 {:user_id 2 :name "user 2" :email "e 2"}]

      

Is there some function in clojure or another library that could do this?

+3


source to share


3 answers


clojure.set / join will do the same.

(use 'clojure.set)

(clojure.set/join a b) ; => #{{:email "e 1", :name "user 1", :user_id 1} {:email "e 2", :name "user 2", :user_id 2}}

      

Without providing a third argument, the function will merge into all shared keys:

(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

(clojure.set/join a b) ; #{{:name "n 1", :url "u 1", :id1 1, :id2 2}}
(clojure.set/join a c) ; #{{:name "n 2", :url "u 2", :id1 2, :id2 3} {:name "n 1", :url "u 1", :id1 1, :id2 2}}

      



To join a and b only to id1:

(clojure.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}}

      

We can even attach to different keys from different collections:

(clojure.set/join a b {:id1 :id2}) ; #{{:name "n 2", :url "u 1", :id1 1, :id2 2}}

      

+8


source


Another option, a little simpler:

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

creates a mapping from :user_id

to all maps containing a given value, vals

gets only the values ​​(each vector) and finally, for each vector of values, they are concatenated.

+3


source


I don't think there is any simple function that does this already, but I could be wrong.

If you know that each one user_id

exists in each sequence, you can simply sort by user_id

and then merge on the corresponding maps:

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

      

If you can't assume that all of the same user_id

ones exist in every sequence, I think you need to do something a little more complicated to match user_id

s. My guess is that if the name card does not have a matching email card, you want to leave the name card unchanged (or vice versa for missing name cards). If not, then one option would be to remove those cards and use the method above.

Here's one way to merge matching names and email cards. We can use the keys user_id

like on the card of cards to match the corresponding cards. First create maps containing all maps with user_ids as keys, for example, for example:

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

      

Then combine the individual cards like this, removing the keys at the end of the process:

(vals (merge-with merge az bz))
; => ({:email "e 2", :name "user 2", :user_id 2} {:email "e 1", :name "user 1", :user_id 1})

      

Putting it all together:

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

      

Make sure it works with missing 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})

      

I wouldn't be surprised if there are simpler or more elegant methods. This is just one strategy that came to my mind.

+1


source







All Articles