Related
I have 2 vectors of maps: employ-base and employ1. I want to merge the 2 vectors where employ1 has higher priority than employ-base. So if employ1 has the records use them, else use the record from employ-base. What is the best way to do it in clojure?
from:
(def employ-base
[{:id 1 :name "Aaron" :income 0}
{:id 2 :name "Ben" :income 0}
{:id 3 :name "Carry" :income 0}])
(def employ1
[{:id 1 :name "Aaron" :income 1000}
{:id 3 :name "Carry" :income 2000}])
to:
(def employ1
[{:id 1 :name "Aaron" :income 1000}
{:id 2 :name "Ben" :income 0}
{:id 3 :name "Carry" :income 2000}])
Assuming :id is unique per employee, you could group the maps by :id then merge each grouping of maps per :id:
(map
#(apply merge (val %))
(merge-with concat
(group-by :id employ-base)
(group-by :id employ1)))
=> ({:id 1, :name "Aaron", :income 1000}
{:id 2, :name "Ben", :income 0}
{:id 3, :name "Carry", :income 2000})
The precedence of merging is maintained by merging employ1 after employe-base, since merge and merge-with prefer the rightmost values.
I am trying to update every line that has an "enjoy-clojure?" that returns true "sanity-rating" to -2 (i.e johnny's sanity-rating would be updated to -2)
(def student-database
{ 0 {:enjoy-clojure? false, :name "jimmy",:sanity-rating 9}
1 { :enjoy-clojure? true, :name "johnny",:sanity-rating 2}
2 { :enjoy-clojure? true, :name "jilly",:sanity-rating 5}
3 { :enjoy-clojure? true, :name "janey",:sanity-rating 8}
4 {:enjoy-clojure? false, :name "jelly",:sanity-rating 10}})
I am new to Clojure and have tried researching update and assoc and can't really seem to find a way to update multiple elements ((assoc student-database [0 :sanity-rating] -2) only returns updates one element). To filter student-database to take out the student's who returned true I have
(defn unhinged?
[record]
(:enjoy-clojure? record))
(defn minus-two-students
[student-database]
(filter #(unhinged? %)
(map student-database [0 1 2 3 4])))
And returns
({:enjoy-clojure? true, :name "johnny", :sanity-rating 2} {:enjoy-clojure?
true, :name "jilly", :sanity-rating 5} {:enjoy-clojure? true, :name
"janey", :sanity-rating 8})
Which works great but I also need it to update all their sanity-rating to -2. Any helps/tips would be much appreciated.
Here comes the reduce-kv version!
(defn adjust-sanity [student]
(if (:enjoy-clojure? student)
(assoc student :sanity-rating -2)
student))
(reduce-kv (fn [m k v] (assoc m k (adjust-sanity v)))
{}
student-database)
=>
{0 {:enjoy-clojure? false, :name "jimmy", :sanity-rating 9},
1 {:enjoy-clojure? true, :name "johnny", :sanity-rating -2},
2 {:enjoy-clojure? true, :name "jilly", :sanity-rating -2},
3 {:enjoy-clojure? true, :name "janey", :sanity-rating -2},
4 {:enjoy-clojure? false, :name "jelly", :sanity-rating 10}}
Or another option with a helper function to update all a map's values:
(defn update-vals [m f]
(reduce-kv (fn [m' k v] (assoc m' k (f v))) {} m))
(update-vals student-database adjust-sanity)
You didn't say in your question that you want the function to returns only (= enjoy-clojure? true) records, but from your comments in the other answers, I feel that's what you really want.
So maybe this?
(defn unhinged?
[record]
(:enjoy-clojure? record))
(defn minus-two-students
[student-database]
(->> student-database
vals
(filter unhinged?)
(map #(assoc % :sanity-rating -2))))
Output will be
({:enjoy-clojure? true, :name "johnny", :sanity-rating -2}
{:enjoy-clojure? true, :name "jilly", :sanity-rating -2}
{:enjoy-clojure? true, :name "janey", :sanity-rating -2})
To update the entire db, you could do:
(def student-database
{0 {:enjoy-clojure? false, :name "jimmy",:sanity-rating 9}
1 { :enjoy-clojure? true, :name "johnny",:sanity-rating 2}
2 { :enjoy-clojure? true, :name "jilly",:sanity-rating 5}
3 { :enjoy-clojure? true, :name "janey",:sanity-rating 8}
4 {:enjoy-clojure? false, :name "jelly",:sanity-rating 10}})
(defn update-db [db]
(zipmap (keys db)
(map (fn [student]
(cond-> student
(:enjoy-clojure? student)
(assoc :sanity-rating -2)))
(vals db))))
(update-db student-database) ;;=>
{0 {:enjoy-clojure? false, :name "jimmy", :sanity-rating 9},
1 {:enjoy-clojure? true, :name "johnny", :sanity-rating -2} ...}
the simplest would be like this:
(reduce-kv (fn [acc idx row]
(assoc acc idx
(if (:enjoy-clojure? row)
(assoc row :sanity-rating -2)
row)))
{}
student-database)
;;=> {0 {:enjoy-clojure? false, :name "jimmy", :sanity-rating 9},
;; 1 {:enjoy-clojure? true, :name "johnny", :sanity-rating -2},
;; 2 {:enjoy-clojure? true, :name "jilly", :sanity-rating -2},
;; 3 {:enjoy-clojure? true, :name "janey", :sanity-rating -2},
;; 4 {:enjoy-clojure? false, :name "jelly", :sanity-rating 10}}
you can also do something like this:
(reduce-kv (fn [res k {ec? :enjoy-clojure?}]
(if ec?
(assoc-in res [k :sanity-rating] -2)
res))
student-database
student-database)
;;=> {0 {:enjoy-clojure? false, :name "jimmy", :sanity-rating 9},
;; 1 {:enjoy-clojure? true, :name "johnny", :sanity-rating -2},
;; 2 {:enjoy-clojure? true, :name "jilly", :sanity-rating -2},
;; 3 {:enjoy-clojure? true, :name "janey", :sanity-rating -2},
;; 4 {:enjoy-clojure? false, :name "jelly", :sanity-rating 10}}
My "(list-projects)" method queries out a map from a SQLITE database.
(doall (apply prn (pm.models.db/list-projects)))
pm.core==>{:id 1, :name "MyTestProj", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 2, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 3, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 4, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 5, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 6, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 7, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 8, :name "", :owner "", :date strong text"2017-12-19 13:12:45"}
I would like to populate a seesaw.core/table (a Java JTable) with those results using the :row property when I construct the seesaw.core/frame/mig-panel/table (JFrame/JPanel/JTable).
(def main-panel
(mig-panel
:constraints ["fill, ins 0"]
:items [[(seesaw.core/table
:id :tbl1
:size [640 :by 480]
:model [:columns [:id :name :owner :date]
:rows [(doall (apply prn (pm.models.db/list-projects)))]
]) "grow"]
]))
2. Unhandled clojure.lang.Compiler$CompilerException
Error compiling form-init4108264894568019320.clj at (44:16)
ang.Thread/run
1. Caused by java.lang.IllegalArgumentException
row must be a map or vector, got null
If I insert the map {:id 1, :name "MyTestProj", :owner "mbc", :date "2017-12-19 13:12:45"} {:id........}{}{}{}{}
directly, it works fine, rows in the table are properly populated, so format of the output of (doall (apply prn (pm.models.db/list-projects))) is what I need.
How do I retrieve the (I presume) lazy-seq in the context of a seesaw.core/mig-panel?
Thanks
Mortimer
pm.core> (pm.models.db/list-projects)
({:id 1, :name "MyTestProj", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 2, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 3, :name "newproject1", :owner "mbc", :date "2017-12-19 13:12:45"} {:id 4, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} {:id 5, :name "abc", :owner "def", :date "2017-12-19 13:12:45"} )
pm.core>
(pm.models.db/list-projects) results in a list that can be used directly as the value for the :rows key (no brackets).
:rows (pm.models.db/list-projects)
Thanks to Carcigenicate for the suggestions.
What's the idiomatic way of merging two lists of maps in Clojure where each map entry is identified by an id key?
What's an implementation for foo so that
(foo '({:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3})
'({:id 5 :value 5}
{:id 2 :value 2}
{:id 3 :value 3}
{:id 1 :value 1}
{:id 4 :value 4})) => '({:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3}
{:id 4 :value 4}
{:id 5 :value 5})
is true?
(defn merge-by
"Merges elems in seqs by joining them on return value of key-fn k.
Example: (merge-by :id [{:id 0 :name \"George\"}{:id 1 :name \"Bernie\"}]
[{:id 2 :name \"Lara\"}{:id 0 :name \"Ben\"}])
=> [{:id 0 :name \"Ben\"}{:id 1 :name \"Bernie\"}{:id 2 :name \"Lara\"}]"
[k & seqs]
(->> seqs
(map (partial group-by k))
(apply merge-with (comp vector
(partial apply merge)
concat))
vals
(map first)))
How about this:
(defn foo [& colls]
(map (fn [[_ equivalent-maps]] (apply merge equivalent-maps))
(group-by :id (sort-by :id (apply concat colls)))))
This is generalized so that you can have an arbitrary number of input sequences, and an arbitrary grouping selector:
(def a [{:id 5 :value 5}
{:id 2 :value 2}
{:id 3 :value 3}
{:id 1 :value 1}
{:id 4 :value 4}])
(def b [{:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3}])
(def c [{:id 1 :bar true :value 1}
{:id 2 :bar false :value 2}
{:id 3 :value 3}
{:id 4 :value 4}
{:id 5 :value 5}])
(defn merge-vectors
[selector & sequences]
(let [unpack-grouped (fn [group]
(into {} (map (fn [[k [v & _]]] [k v]) group)))
grouped (map (comp unpack-grouped (partial group-by selector))
sequences)
merged (apply merge-with merge grouped)]
(sort-by selector (vals merged))))
(defn tst
[]
(= c
(merge-vectors :id a b)))
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 i.e. 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"}}