简体   繁体   中英

Clojure, merging two array of maps

I have two arrays of maps

1st is [{:a 1 :b 2 :d 6} {:a 2 :b 2} {:a 7 :b 7}]

2nd is [{:a 3 :c 3 :e 9 :y 7} {:a 2 :b 6 :c 8}]

depending on the value of a ie if its matches in 2nd array the '2nd map' should be merged with '1st map' and the resultant array of maps should be

Res should be [{:a 1 :b 2 :d 6} {:a 2 :b 6 :c 8} {:a 7 :b 7} {:a 3 :c 3 :e 9 :y 7}]

Can anyone help me on this. Thanks in advance.

Here you go:

user> (def xs [{:a 1 :b 2 :d 6} {:a 2 :b 2} {:a 7 :b 7}])
#'user/xs
user> (def ys  [{:a 3 :c 3 :e 9 :y 7} {:a 2 :b 6 :c 8}])
#'user/ys
user> (for [[a ms] (group-by :a (concat xs ys))] (apply merge ms))
({:a 1, :b 2, :d 6} {:a 2, :c 8, :b 6} {:a 7, :b 7} {:y 7, :a 3, :c 3, :e 9})

This data structure looks very unwieldy to me nevertheless here's my take:

(defn key-by-a [coll]
  "Convert a list of maps to a map of maps keyed by their vals at :a"
  (apply hash-map (mapcat (juxt :a identity) coll)))

(defn merge-map-lists [l1 l2]
  (->> [l1 l2]
    (map key-by-a)
    (apply merge-with merge)
    (vals)))

One thing it doesn't do is maintaining order of the input lists but since it is not clear which list decides (both might have same keys in different orders) I left that out.

maybe clojure.set/join is what you want:

here is the docs of clojure.set/join :

user=> (def animals #{{:name "betsy" :owner "brian" :kind "cow"}
                      {:name "jake"  :owner "brian" :kind "horse"}
                      {:name "josie" :owner "dawn"  :kind "cow"}})

user=> (def personalities #{{:kind "cow" :personality "stoic"}
                            {:kind "horse" :personality "skittish"}})
#'user/personalities
user=> (join animals personalities)

#{{:owner "dawn",  :name "josie", :kind "cow",   :personality "stoic"}
  {:owner "brian", :name "betsy", :kind "cow",   :personality "stoic"}
  {:owner "brian", :name "jake",  :kind "horse", :personality "skittish"}}

user=> (join animals personalities)
#{{:kind "horse", :owner "brian", :name "jake", :species "cow", :personality "stoic"}
  {:kind "cow", :owner "dawn", :name "josie", :species "cow", :personality "stoic"}
  {:kind "horse", :owner "brian", :name "jake", :species "horse", :personality "skittish"}
  {:kind "cow", :owner "brian", :name "betsy", :species "cow", :personality "stoic"}
  {:kind "cow", :owner "dawn", :name "josie", :species "horse", :personality "skittish"}
  {:kind "cow", :owner "brian", :name "betsy", :species "horse", :personality "skittish"}}


;; Notice that "Jake" is both a horse and a cow in the first line. That's 
;; likely not what you want. You can tell `join` to only produce output 
;; where the `:kind` value is the same as the `:species` value like this:

user=> (join animals personalities {:kind :species})
#{{:kind "cow", :owner "dawn", :name "josie", :species "cow", :personality "stoic"}
  {:kind "horse", :owner "brian", :name "jake", :species "horse", :personality "skittish"}
  {:kind "cow", :owner "brian", :name "betsy", :species "cow", :personality "stoic"}}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM