Converting a vector of maps to map of maps in Clojure - clojure

I've a vector of maps like this:
[{:categoryid 1, :categoryname "foo" }
{:categoryid 2, :categoryname "bar" }
{:categoryid 3, :categoryname "baz" }]
and would like to generate a map of maps like this for searching by categoryname
{"foo" {:categoryid 1, :categoryname "foo" },
"bar" {:categoryid 2, :categoryname "bar" },
"baz" {:categoryid 3, :categoryname "baz" }}
How can I do this?

Another way: (into {} (map (juxt :categoryname identity) [...]))

(reduce (fn [m {catname :categoryname :as input}]
(assoc m catname input))
{}
[{:categoryid 1, :categoryname "foo" }
{:categoryid 2, :categoryname "bar" }
{:categoryid 3, :categoryname "baz" }])
Better yet,
(#(zipmap (map :categoryname %) %)
[{:categoryid 1, :categoryname "foo" }
{:categoryid 2, :categoryname "bar" }
{:categoryid 3, :categoryname "baz" }])

(ns code.groupby
(:use clojure.contrib.seq-utils))
(def vector-of-maps [ {:categoryid 1, :categoryname "foo" }
{:categoryid 2, :categoryname "bar" }
{:categoryid 3, :categoryname "baz" } ])
(group-by :categoryname vector-of-maps)
Gives you a map of Vectors of maps
{"bar" [{:categoryid 2, :categoryname "bar"}],
"baz" [{:categoryid 3, :categoryname "baz"}],
"foo" [{:categoryid 1, :categoryname "foo"}]}
(which I now realise isn't what you wanted...sorry)

I haven't tested it in Clojure, but in ClojureScript this variant appears to be faster than others:
(reduce #(assoc %1 (:categoryname %2) %2) {} [...])

Related

Clojure how to mix/merge two maps

I'm new in clojure and I need mix a very complex (for me) maps that have inside a Vector to mix up.
Orginal map:
{:cars {:previous-page nil, :next-page 2, :count 33, :items [{:id 1, :name "test1"}, {:id 2, :name "test2"}]},
:trucks {:previous-page nil, :next-page 2, :count 11, :items [{:id 1, :name "test3"}, {:id 2, :name "test4"}]},
:boats {:previous-page nil, :next-page 2, :count 22, :items [{:id 1, :name "test5"}, {:id 2, :name "test6"}]}}
Second map:
{:cars {:previous-page 2, :next-page 3, :count 33, :items [{:id 3, :name "test7"}, {:id 4, :name "test8"}]},
:trucks {:previous-page 3, :next-page 4, :count 11, :items [{:id 3, :name "test9"}, {:id 4, :name "test10"}]},
:boats {:previous-page 4, :next-page 5, :count 22, :items [{:id 3, :name "test11"}, {:id 4, :name "test12"}]}}
I need to mix this two maps in only one:
{:cars {:previous-page 2, :next-page 3, :count 33, :items [{:id 1, :name "test1"}, {:id 2, :name "test2"},{:id 3, :name "test7"}, {:id 4, :name "test8"}]},
:trucks {:previous-page 3, :next-page 4, :count 11, :items [{:id 1, :name "test3"}, {:id 2, :name "test4"},{:id 3, :name "test9"}, {:id 4, :name "test10"}]},
:boats {:previous-page 4, :next-page 5, :count 22, :items [{:id 1, :name "test5"}, {:id 2, :name "test6"},{:id 3, :name "test11"}, {:id 4, :name "test12"}]}}
You can use merge-with to combine maps with an arbitrary function. Here there does not seem to be one "rule" though - for :previous-page, :next-page, and :count seem to be "last one wins" but :items seems to be something like#(into [] %)`. Once you can state that rule clearly as a function:
(fn combine [map1 map2] ...)
You can then easily combine the maps with merge-with using whatever combine you've defined.

Clojure - in a vector of hashmaps return the lowest hashmap where two keys match

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

Merging two lists of maps where entries are identified by id in Clojure

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

replace nil values with zero in hash map

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

How can I sort a clojure set of maps?

I have a set of maps something like this:
#{
{:name "a" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
{:name "c" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
}
: and I want to get to an ordered list, much like sql order-by name:
[
{:name "a" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
{:name "a" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "b" :value "b" ... more stuff here}
{:name "c" :value "b" ... more stuff here}
]
: how can I do this?
Function sort-by is what you're looking for:
(def s
#{
{:name "d" :value "b" }
{:name "b" :value "b" }
{:name "c" :value "b" }
})
(sort-by :name s)
sort-by is a great answer, and makes the code a lot better in the simple cases where it works. Additionally the sort function can take a function to extract the comparason key from each map incase you need to do some processing on each item. In this example i use a sort function that extracts each name and then does a string compare on them.
(sort #(compare (:name %1) (:name %2)) data)
=> ({:name "a", :value "b"} {:name "b", :value "b"} {:name "c", :value "b"})
this is useful if your collections had different names to be compared:
(sort #(compare (:value %1) (:name %2)) data)
=> ({:name "a", :value "b"} {:name "c", :value "b"} {:name "b", :value "b"})
the compare function is a better version of java's .compareto() because it properly handles nil and compares clojure collections properly. is is basically a short cut for using the . opperator in most cases
(sort #(. (:name %1) (compareTo (:name %2))) data)
=> ({:name "a", :value "b"} {:name "b", :value "b"} {:name "c", :value "b"})
(def set-of-maps #{{:name "d"}, {:name "b"}, {:name "a"}})
-> clojure.core/sort-by
(sort-by :name set-of-maps)
; => ({:name "a", :value "b"} {:name "c", :value "b"} {:name "d", :value "b"})
sort-by is what you want, but please post snippets that are actually valid code; I wasted a fair bit of time trying to figure out a problem that wound up being because #{{:name "a" :value "b"} {:name "a" :value "b"}} makes the reader barf.
I believe the snippet from the joy of clojure is the neatest.
(def plays [{:band "Burial", :plays 979, :loved 9}
{:band "Eno", :plays 2333, :loved 15}
{:band "Bill Evans", :plays 979, :loved 9}
{:band "Magma", :plays 2665, :loved 31}])
(def sort-by-loved-ratio (partial sort-by #(/ (:plays %) (:loved %))))