Question

What is an idiomatic way to add values from one vector of maps to the other vector of maps that have the same value of the given key, where maps don't have the same name of the keys. Ie.

(def v1 
[{:name "name1" :address "address1"} 
{:name "name2" :address "address2"}])

(def v2 
[{:title "name1" :datofBirth "1-1-1971"} 
{:title "name3" :dateOfBirth "2-1-1971"}]) 

And the merge result should be

res 
 [{:name "name1" :address "address1" :dateofBirth "1-1-1971"}
{:name "name2" :address "address2" :dateOfBirth nil}]

The call should look something like this

 (join v1 v2 :name :title)

And the body should look something like this, maybe assoc-in should be used

 (assoc-in v1 [(map-where-the-values-are-the-same) :key2] (value-from-the-2nd-map))
Was it helpful?

Solution

For inner joins, you can use clojure.set/join:

(clojure.set/join v1 v2 {:name :title})

You're sample result seems to indicate that you want to perform a left join, however. If so, you may want to look into some existing Stack Overflow questions on outer joins in Clojure; for example, my answer to the recent Full outer join two sequences of maps in Clojure question provides a performant solution to the full outer join problem, which could straightforwardly be adapted to produce left joins instead.

Here's one possible adaptation:

(defn left-join [key-map xs ys]
  (let [kes (seq key-map)
        lks (mapv key kes)
        rks (mapv val kes)
        gxs (group-by #(mapv (fn [k] (get % k)) lks) xs)
        gys (dissoc (group-by #(mapv (fn [v] (get % v)) rks) ys) nil)
        kvs (keys gxs)]
    (persistent!
     (reduce (fn [out kv]
               (let [l (get gxs kv)
                     r (get gys kv)]
                 (if (seq r)
                   (reduce (fn [out m1]
                             (reduce (fn [out m2]
                                       (conj! out (merge m1 m2)))
                                     out
                                     r))
                           out
                           l)
                   (reduce conj! out l))))
             (transient [])
             kvs))))

At the REPL:

(left-join {:name :title} v1 v2)
;= [{:name "name1", :datofBirth "1-1-1971", :title "name1",
     :address "address1"}
    {:name "name2", :address "address2"}]

If you'd rather dissoc :title from the resulting maps and add the missing keys to the records from the left side for which there are no corresponding records on the right side, you could modify the function slightly or just do it in a postprocessing step.

OTHER TIPS

It is better to make small function then call using thread macro. Find code in bellow.

 (def v1 [{:name "name1" :address "address1"} 
     {:name "name2" :address "address2"}
     {:name "name4" :address "address2"}])

  (def v2 [{:title "name1" :datofBirth "1-1-1971"} 

     {:title "name3" :dateOfBirth "2-1-1971"}
     {:title "name4" :dateOfBirth "2-1-1971"}
     ]) 

(defn filter [k1 k2]
  (fn [d1 d2]
    (for [m1 d1
          m2 d2 
      :let [n1 (k1 m1)
            t1 (k2 m2)]
      :when (= n1 t1) ]
    (merge m1 (dissoc m2 k2)))))


(defn data-join [k1]
  (fn [d1 d2]
    (reduce (fn [acc t] 
          (map (fn [v1 v2] 
                (if (= (k1 v1)
                       (k1 v2))
                  v1 v2)) (repeat t) acc))
        d1 d2)))

    (->> v2 
       ((filter :name :title) v1  )
       ((data-join :name) v1))

Filter function filter data from v2. Then join result with first. It is possible to make smaller both of the function.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top