I have a hash map in clojure which contains some nil values. I am trying to group the data and sum the values, this gives me a null pointer due to the nil values. Can someone please advise on how I can iterate through the hash map and replace all nil values with integer 0?
(def data [{:MEDAL "SILVER" :EMEA 1 :NA nil :ASPAC 3}
{:MEDAL "GOLD" :EMEA 1 :NA 2 :ASPAC 3}
{:MEDAL "GOLD" :EMEA nil :NA 2 :ASPAC nil}
{:MEDAL "BRONZE" :EMEA nil :NA 2 :ASPAC 3}
{:MEDAL "SILVER" :EMEA 1 :NA 2 :ASPAC 3}
{:MEDAL "GOLD" :EMEA 1 :NA nil :ASPAC nil}
{:MEDAL "BRONZE" :EMEA 1 :NA 2 :ASPAC 3}])
Thanks
(map (fn [m]
(into {}
(map (fn [[k v]]
[k (if (nil? v) 0 v)]) m)))
data)
=> ({:EMEA 1, :NA 0, :MEDAL "SILVER", :ASPAC 3}
{:EMEA 1, :NA 2, :MEDAL "GOLD", :ASPAC 3}
{:EMEA 0, :NA 2, :MEDAL "GOLD", :ASPAC 0}
{:EMEA 0, :NA 2, :MEDAL "BRONZE", :ASPAC 3}
{:EMEA 1, :NA 2, :MEDAL "SILVER", :ASPAC 3}
{:EMEA 1, :NA 0, :MEDAL "GOLD", :ASPAC 0}
{:EMEA 1, :NA 2, :MEDAL "BRONZE", :ASPAC 3})
Rather than replace the nil values with zeros, you might consider just working with them by using keep. For example:
(apply + (keep :NA data))
; 10
(apply + (keep (fn [m] (when (= (:MEDAL m) "SILVER") (:EMEA m))) data))
; 2
You could use fnil.
(->> data
(apply merge-with conj (zipmap [:EMEA :NA :MEDAL :ASPAC] (repeat [])))
(reduce-kv #(assoc %1 %2 (reduce (fnil + 0 0) 0 %3)) {}))
Obviously :MEDAL needs other treatment.
I am assuming that you want to group the data by :MEDAL and then add EMEA, ASPAC etc., right?
;; Helper function
(defn batch-hashmaps
"Applies operation on values of keys in maps. filterf filters valid values.
Example: (batch-hashmaps + [:b :c] number? {:a 30, :b 50, :c :cheese} {:b 30, :c 40})
=> {:b 80, :c 40}"
[operation keys filterf & maps]
(apply conj {}
(for [key keys]
[key (apply operation (filter filterf (map #(key %) maps)))])))
(for [medal (set (map #(:MEDAL %) data))]
(assoc (apply (partial batch-hashmaps + [:EMEA :NA :ASPAC] number?) (filter #(= medal (:MEDAL %)) data) ) :MEDAL medal))
This should be the desired result:
({:MEDAL "GOLD", :EMEA 2, :NA 4, :ASPAC 3}
{:MEDAL "SILVER", :EMEA 2, :NA 2, :ASPAC 6}
{:MEDAL "BRONZE", :EMEA 1, :NA 4, :ASPAC 6})
And here is another quick and dirty thing.
(defn sum-medal
[medal data]
(assoc (apply (partial merge-with (fn [& [_ _ :as vals]] (apply + (filter number? vals)))) (filter #(= (:MEDAL %) medal) data)) :MEDAL medal))
Related
How to make the following code generate a similar visualization to pprint in HTML?
(let [clj-structure {:ui {:selected #{[:A 3]}}
:domain
{:C {0 {:value 12}}
:D {0 {:dependants [[:C 0]] :string "3" :value "3"}
1 {:dependants [[:C 0]] :string "4" :value "4"}}
:E {2 {:dependants [] :string "2" :value "2"}
3 {:dependants [] :string "10" :value "10"}}}}]
[:p (str clj-structure)])
(let [clj-structure {:ui {:selected #{[:A 3]}}
:domain
{:C {0 {:value 12}}
:D {0 {:dependants [[:C 0]] :string "3" :value "3"}
1 {:dependants [[:C 0]] :string "4" :value "4"}}
:E {2 {:dependants [] :string "2" :value "2"}
3 {:dependants [] :string "10" :value "10"}}}}]
[:pre (with-out-str (cljs.pprint/pprint clj-structure))])
So I have this:
[{:a ["x" "y"], :b "foo"}
{:a ["x" "y" "z"], :b "bar"}]
And want this:
[{:a "x", :b "foo"}
{:a "y", :b "foo"}
{:a "x", :b "bar"}
{:a "y", :b "bar"}
{:a "z", :b "bar"}]
How can I do this?
for is really nice for known levels of nesting:
(for [x [{:a ["x" "y"], :b "foo"}
{:a ["x" "y" "z"], :b "bar"}]
a (:a x)]
(assoc x :a a))
You can use mapcat:
(def c [{:a ["x" "y"], :b "foo"}
{:a ["x" "y" "z"], :b "bar"}])
(mapcat (fn [{:keys [a] :as m}] (map #(assoc m :a %) a)) c)
I have a vector of hashmaps with a format similar to the following:
[{:a 1 :b 2} {:a 3 :b 4} {:a 1 :b 6} {:a 3 :b 9} {:a 5 :b 1} {:a 6 :b 1}]
I would like to filter out the lowest :b value for matching :a values, so if two :a values are the same e.g. {:a 1 :b 2},{:a 1 :b 6} it should return: {:a 1 :b 2} as 2 is lower than 6
So for the vector above I would like to get:
[{:a 1 :b 2} {:a 3 :b 4} {:a 5 :b 1} {:a 6 :b 1}]
I have tried a few things but I am a bit stuck, any help would be appreciated, thanks.
Your original direction was correct. You only missed the grouping part:
(def test [{:a 1 :b 2} {:a 3 :b 4} {:a 1 :b 6} {:a 3 :b 9} {:a 5 :b 1} {:a 6 :b 1}])
(defn min-map [m]
(map (partial apply min-key :b) (vals (group-by :a m))))
(min-map test)
=> ({:a 1, :b 2} {:a 3, :b 4} {:a 5, :b 1} {:a 6, :b 1})
First we group the the list of maps by the key :a, and extract the values of it. We then examine each group and find the smallest value using min-key by key :b
So I thought about it for a bit and I now have an answer (albeit not a very good one):
(defn contains [a vect]
(apply min-key :b(filter #(= (:a %) (:a a))vect))
)
(defn starter []
(let [tester [{:a 1 :b 2} {:a 3 :b 4} {:a 1 :b 6} {:a 3 :b 9} {:a 5 :b 1} {:a 6 :b 1}]]
(vec(distinct(map #(contains % tester)tester)))
)
)
Thanks for everyones help, If you have any critiques or a better solution please post it.
With the dependency
[tupelo "0.1.68"]
we can write the following code. I left in lots of spy expressions so
(ns clj.core
(:use tupelo.core)
(:require [clojure.core :as clj]
[schema.core :as s]
[tupelo.types :as tt]
[tupelo.schema :as ts]
))
; Prismatic Schema type definitions
(s/set-fn-validation! true) ; #todo add to Schema docs
(def data [ {:a 1 :b 2} {:a 3 :b 4} {:a 1 :b 6} {:a 3 :b 9} {:a 5 :b 1} {:a 6 :b 1} ] )
(defn accum-smallest-b-entries
[cum-map-a2b
; A map where both keys and vals are simple 1-entry maps
; like: {:a 1} -> {:b 2}
; {:a 2} -> {:b 9}
new-a-b-map
; next entry, like {:a 1 :b 2}
]
(newline)
(println "---------------------------------")
(let [new-a-map (select-keys new-a-b-map [:a] ) ; like {:a 1}
_ (spyx new-a-map)
new-b-map (select-keys new-a-b-map [:b] ) ; like {:b 2}
_ (spyx new-b-map)
curr-b-map (get cum-map-a2b new-a-map)
_ (spyx curr-b-map)
next-b-map (if (or (nil? curr-b-map)
(< (grab :b new-b-map) (grab :b curr-b-map)))
new-b-map
curr-b-map)
_ (spyx next-b-map)
]
(spyx (assoc cum-map-a2b new-a-map next-b-map))))
(def a2b-map (reduce accum-smallest-b-entries {} data))
(spyx a2b-map)
(defn combine-keyvals-from-a2b-map
[cum-result
; final result like: [ {:a 1 :b 2}
; {:a 2 :b 9} ]
a2b-entry
; next entry from a2b-map like [ {:a 5} {:b 1} ]
]
(newline)
(println "combine-keyvals-from-a2b-map")
(println "---------------------------------")
(spyx cum-result)
(spyx a2b-entry)
(let [combined-ab-map (glue (first a2b-entry) (second a2b-entry))
_ (spyx combined-ab-map)
new-result (append cum-result combined-ab-map)
_ (spyx new-result)
]
new-result))
(def a-and-b-map (reduce combine-keyvals-from-a2b-map [] a2b-map))
(spyx a-and-b-map)
(defn -main [] )
Running the code we get:
---------------------------------
new-a-map => {:a 1}
new-b-map => {:b 2}
curr-b-map => nil
next-b-map => {:b 2}
(assoc cum-map-a2b new-a-map next-b-map) => {{:a 1} {:b 2}}
---------------------------------
new-a-map => {:a 3}
new-b-map => {:b 4}
curr-b-map => nil
next-b-map => {:b 4}
(assoc cum-map-a2b new-a-map next-b-map) => {{:a 1} {:b 2}, {:a 3} {:b 4}}
---------------------------------
new-a-map => {:a 1}
new-b-map => {:b 6}
curr-b-map => {:b 2}
next-b-map => {:b 2}
(assoc cum-map-a2b new-a-map next-b-map) => {{:a 1} {:b 2}, {:a 3} {:b 4}}
---------------------------------
new-a-map => {:a 3}
new-b-map => {:b 9}
curr-b-map => {:b 4}
next-b-map => {:b 4}
(assoc cum-map-a2b new-a-map next-b-map) => {{:a 1} {:b 2}, {:a 3} {:b 4}}
---------------------------------
new-a-map => {:a 5}
new-b-map => {:b 1}
curr-b-map => nil
next-b-map => {:b 1}
(assoc cum-map-a2b new-a-map next-b-map) => {{:a 1} {:b 2}, {:a 3} {:b 4}, {:a 5} {:b 1}}
---------------------------------
new-a-map => {:a 6}
new-b-map => {:b 1}
curr-b-map => nil
next-b-map => {:b 1}
(assoc cum-map-a2b new-a-map next-b-map) => {{:a 1} {:b 2}, {:a 3} {:b 4}, {:a 5} {:b 1}, {:a 6} {:b 1}}
a2b-map => {{:a 1} {:b 2}, {:a 3} {:b 4}, {:a 5} {:b 1}, {:a 6} {:b 1}}
combine-keyvals-from-a2b-map
---------------------------------
cum-result => []
a2b-entry => [{:a 1} {:b 2}]
combined-ab-map => {:a 1, :b 2}
new-result => [{:a 1, :b 2}]
combine-keyvals-from-a2b-map
---------------------------------
cum-result => [{:a 1, :b 2}]
a2b-entry => [{:a 3} {:b 4}]
combined-ab-map => {:a 3, :b 4}
new-result => [{:a 1, :b 2} {:a 3, :b 4}]
combine-keyvals-from-a2b-map
---------------------------------
cum-result => [{:a 1, :b 2} {:a 3, :b 4}]
a2b-entry => [{:a 5} {:b 1}]
combined-ab-map => {:a 5, :b 1}
new-result => [{:a 1, :b 2} {:a 3, :b 4} {:a 5, :b 1}]
combine-keyvals-from-a2b-map
---------------------------------
cum-result => [{:a 1, :b 2} {:a 3, :b 4} {:a 5, :b 1}]
a2b-entry => [{:a 6} {:b 1}]
combined-ab-map => {:a 6, :b 1}
new-result => [{:a 1, :b 2} {:a 3, :b 4} {:a 5, :b 1} {:a 6, :b 1}]
a-and-b-map => [{:a 1, :b 2} {:a 3, :b 4} {:a 5, :b 1} {:a 6, :b 1}]
In hindsight, it could be simplified more if we were guarenteed that each input map was like {:a :b }, since we could simplify it to a series of 2-d points like [n m], since the keywords :a and :b would be reduntant.
Here is a better answer using the group-by function:
(ns clj.core
(:use tupelo.core)
(:require [clojure.core :as clj]
[schema.core :as s]
[tupelo.types :as tt]
[tupelo.schema :as ts]
))
; Prismatic Schema type definitions
(s/set-fn-validation! true) ; #todo add to Schema docs
(def data [ {:a 1 :b 2} {:a 3 :b 4} {:a 1 :b 6} {:a 3 :b 9} {:a 5 :b 1} {:a 6 :b 1} ] )
(def data-by-a (group-by :a data))
; like { 1 [{:a 1, :b 2} {:a 1, :b 6}],
; 3 [{:a 3, :b 4} {:a 3, :b 9}],
; 5 [{:a 5, :b 1}],
; 6 [{:a 6, :b 1}] }
(spyx data-by-a)
(defn smallest-map-by-b
[curr-result ; like {:a 1, :b 2}
next-value] ; like {:a 1, :b 6}
(if (< (grab :b curr-result)
(grab :b next-value))
curr-result
next-value))
(defn find-min-b
"Return the map with the smallest b value"
[ab-maps] ; like [ {:a 1, :b 2} {:a 1, :b 6} ]
(reduce smallest-map-by-b
(first ab-maps) ; choose 1st as init guess at result
ab-maps))
(def result
(glue
(for [entry data-by-a] ; entry is MapEntry like: [ 1 [{:a 1, :b 2} {:a 1, :b 6}] ]
(spyx (find-min-b (val entry)))
)))
(spyx result)
(defn -main [] )
which produces the result
data-by-a => {1 [{:a 1, :b 2} {:a 1, :b 6}], 3 [{:a 3, :b 4} {:a 3, :b 9}], 5 [{:a 5, :b 1}], 6 [{:a 6, :b 1}]}
(find-min-b (val entry)) => {:a 1, :b 2}
(find-min-b (val entry)) => {:a 3, :b 4}
(find-min-b (val entry)) => {:a 5, :b 1}
(find-min-b (val entry)) => {:a 6, :b 1}
result => [{:a 1, :b 2} {:a 3, :b 4} {:a 5, :b 1} {:a 6, :b 1}]
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"}}