ClojureScript array-map and sorted-map-by bug? - clojure

(let [mymap (into {} (for [x (shuffle (rest (clojure.string/split "abcdefghijklmnopqrstuvwxyz" #"")))]
{x {:idx (rand-int 24)}}))]
(into (sorted-map-by (fn [k1 k2]
(compare [(get-in mymap [k1 :idx]) k1]
[(get-in mymap [k2 :idx]) k2])))
mymap))
or
(let [mymap (into {} (for [x (shuffle (rest (clojure.string/split "abcdefghijklmnopqrstuvwxyz" #"")))]
{x {:idx (rand-int 24)}}))]
(->> mymap
(sort-by (fn [[_ m]] (:idx m)))
(into (array-map))))
=> {"d" {:idx 22}, "n" {:idx 22}, "z" {:idx 14}, "w" {:idx 11}, "s" {:idx 17}, "f" {:idx 20}, "e" {:idx 19}, "q" {:idx 12}, "p" {:idx 10}, "j" {:idx 0}, "x" {:idx 20}, "v" {:idx 14}, "a" {:idx 1}, "t" {:idx 13}, "i" {:idx 21}, "k" {:idx 16}, "b" {:idx 23}, "r" {:idx 3}, "y" {:idx 18}, "g" {:idx 0}, "l" {:idx 16}, "u" {:idx 20}, "h" {:idx 10}, "m" {:idx 16}, "o" {:idx 11}, "c" {:idx 4}}
If we limit the amount to 8 is ok.
(let [mymap (into {} (for [x (shuffle (rest (clojure.string/split "abcdefghijklmnopqrstuvwxyz" #"")))]
{x (rand-int 24)}))]
(into (sorted-map-by (fn [k1 k2]
(compare [(get mymap k1) k1]
[(get mymap k2) k2])))
(take 8 mymap)))
{"z" {:idx 1}, "q" {:idx 6}, "n" {:idx 7}, "s" {:idx 7}, "f" {:idx 9}, "d" {:idx 17}, "w" {:idx 18}, "e" {:idx 21}}
may be associated with Clojurescript Array-Map order

What version of clojurescript are you using? The first form in cljs 0.0-3308 keeps the sort order as you expect:
(println *clojurescript-version*) ;=> "0.0-3308"
(let [mymap (into {} (for [x (shuffle (rest (clojure.string/split "abcdefghijklmnopqrstuvwxyz" #"")))]
{x {:idx (rand-int 24)}}))]
(into (sorted-map-by (fn [k1 k2]
(compare [(get-in mymap [k1 :idx]) k1]
[(get-in mymap [k2 :idx]) k2])))
mymap))
;=> {"d" {:idx 0}, "i" {:idx 0}, "w" {:idx 5}, "n" {:idx 6}, "q" {:idx 9}, "y" {:idx 9}, "k" {:idx 10}, "m" {:idx 10}, "x" {:idx 10}, "c" {:idx 12}, "h" {:idx 12}, "l" {:idx 13}, "v" {:idx 14}, "s" {:idx 15}, "z" {:idx 16}, "j" {:idx 17}, "p" {:idx 17}, "t" {:idx 17}, "o" {:idx 18}, "r" {:idx 19}, "b" {:idx 20}, "e" {:idx 20}, "a" {:idx 21}, "f" {:idx 21}, "g" {:idx 22}, "u" {:idx 23}}
Are you using a version older than 2411? That's the first release that includes the fix for the issue explained in the answer to the question you linked. It was fixed on this commit which was first released in version 2411.

Related

Manipulation two vectors of maps in clojure

I need to implement a function taking two vectors of maps and return a kind of combined one vector of maps. Details are below:
input 1:
[{:id 1 :car "A" :price 10}{:id 2 :car "B" :price 20}{:id 3 :car "C" :price 30}]
input 2:
[{:id 4 :car "A" :price 5}{:id 5 :car "B" :price 30}{:id 6 :car "D" :price 40}]
output:
[{:id 4 :car "A" :price 5} {:id 2 :car "B" :price 20} {:id 3 :car "C" :price 30} {:id 6 :car "D" :price 40}]
That is, pick up the minimum value of price if :car are the same, or directly add to output with :id.
I have considered using map to get each value to compare within a nested loop but I believe that is not a nice way to do it. Then I learn something like clojure.walk and juxt, but they seem like quite fancy and need more explanations.
The other possible abstract solution I think is to concat them together, and check though each map in vector using flag to check price. Pick up the minimun and remove the larger one.
I hope you can help me and thank you so much!
one way to do this would is to group items by :car and then to find the value with minimum price for every group:
user> (->> (concat data1 data2)
(group-by :car)
vals
(map #(apply min-key :price %)))
;;=>({:id 4, :car "A", :price 5} {:id 2, :car "B", :price 20} {:id 3, :car "C", :price 30} {:id 6, :car "D", :price 40})
you can also do it in one pass with reduce, which is a bit more verbose, but should have better performance:
(defn process [& colls]
(vals (reduce (fn [acc {car :car :as item}]
(if (acc car)
(update acc car (partial min-key :price) item)
(assoc acc car item)))
{}
(apply concat colls))))
user> (process data1 data2)
;;=> ({:id 4, :car "A", :price 5} {:id 2, :car "B", :price 20} {:id 3, :car "C", :price 30} {:id 6, :car "D", :price 40})

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

Reading key with greatest number from set

I have a set of maps as below
#{{:uid 2, :name "book2", :qty "2", :price "2"} {:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}}
Now I want to read value of :uid with greatest number, so in this example I want to get 3.
Can anyone guide me how can I fetch this?
You could also use max-key
(:uid (apply max-key
:uid
#{{:uid 2, :name "book2", :qty "2", :price "2"} {:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}}))
This wouldn't work if the data was as given though, as the uids seem to be strings in some and numbers in other maps. Also, if the set was empty, this would fail.
The built in sorted-set-by function should do the trick:
user> (apply sorted-set-by
#(- (:uid %1) (:uid %2))
#{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}})
#{{:uid 1, :name "book4", :qty "2", :price "2"}
{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :qty "1", :price "1"}}
then take the last one (or reverse the %1 and %2 comparator). Of course the usual trade-offs apply if for instance you where only going to get the value once and or it was a small set then a linear scan would be more efficient.
PS: I changed the data so all the uids are the same type
You could try using reduce
(reduce #(max %1 (:uid %2)) 0 #{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}})
Assuming the uids are non-negative.
EDIT:
It would be better to use a different starting value for the accumulator as recommended by #Thumbnail.
(reduce #(max %1 (:uid %2)) (Long/MIN_VALUE) #{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}})
This should work with negatives as well.
If you just want the number,
(defn max-uid [coll] (apply max (map :uid coll)))
will supply it.
(max-uid #{{:uid 2, :name "book2", :qty "2", :price "2"}
{:uid 3, :name "book3", :price "1", :qty "1"}
{:uid 1, :name "book4", :qty "2", :price "2"}})
;3

Clojure - How merge two/more maps into a single map which have the same value for certain key(s)

I am learning how to idiomatically destruct data structures in Clojure and currently have the following example data
Data:
(def data [
{:category "A", :vertical_name "One", :vertical_id 11}
{:category "B", :vertical_name "Two", :vertical_id 12}
{:category "A", :vertical_name "Three", :vertical_id 13}
{:category "C", :vertical_name "Four", :vertical_id 14}
])
I want to merge two/more maps into a single map which belong to the same Category in the data above,
Expected Output:
{{:category "A", :vertical [{:vertical_id 11, :vertical_name "One"}{:vertical_id 13, :vertical_name "Three"}]}
{:category "B", :vertical {:vertical_id 12, :vertical_name "Two"}}
{:category "C", :vertical {:vertical_id 14, :vertical_name "Four"}}}
I have tried group-by which gives me
{"A" [{:category "A", :vertical_name "One", :vertical_id 11} {:category "A", :vertical_name "Three", :vertical_id 13}],
"B" [{:category "B", :vertical_name "Two", :vertical_id 12}],
"C" [{:category "C", :vertical_name "Four", :vertical_id 14}]}
But this has a lot of redundant information, for example :category "A" is present in all the maps and the format of the output isn't the way I want it.
Thanks in advance for the help.
user> (->> data
(group-by :category)
(map (fn [[k v]]
(let [vertical (map #(dissoc % :category) v)
vertical (if (< (count vertical) 2)
(first vertical)
(vec vertical))]
{:category k
:vertical vertical})))
set)
#{{:category "A", :vertical [{:vertical_name "One", :vertical_id 11} {:vertical_name "Three", :vertical_id 13}]} {:category "B", :vertical {:vertical_name "Two", :vertical_id 12}} {:category "C", :vertical {:vertical_name "Four", :vertical_id 14}}}
This is the format you describe, but for most usage it would be worse than the output group-by provides. Unlike the group-by result, you cannot efficiently look up a given category as the size of the result scales. Further, unlike the result of group-by, sometimes the :vertical key has a collection, and sometimes a single element, and this is a piece of complexity offloaded onto any other code that accesses this data.