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)
Related
We have an atom with some keys and boolean values:
(def btns (reagent.core/atom {:a true :b true :c true}))
I need to change the state of all keys, like this: {:a false :b false :c false}
I tried this, but not good solution, and not working:
(for [btn #btns]
(swap! btns assoc (key btn) false))
you can update multiple keys this way:
user> (def a (atom {:a true :b true :c true}))
;;=> #<Atom#af0fa6a: {:a true, :b true, :c true}>
user> (swap! a into {:b false :c false})
;;=> {:a true, :b false, :c false}
or like this:
user> (swap! a assoc :a false :c false)
;;=> {:a false, :b true, :c false}
if you want to update all the keys in atom to false it could also look like this:
user> (reset! a (zipmap (keys #a) (repeat false)))
;;=> {:a false, :b false, :c false}
or like this:
user> (swap! a #(zipmap (keys %) (repeat false)))
;;=> {:a false, :b false, :c false}
update
also, it is good to abstract out the util function, to make it more readable:
(defn assoc-all-keys [data val]
(zipmap (keys data) (repeat val)))
user> (swap! a assoc-all-keys false)
;;=> {:a false, :b false, :c false}
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}]
I have an Atom, like x:
(def x (atom {:name "A"
:id 1
:children [{:name "B"
:id 2
:children []}
{:name "C"
:id 3
:children [{:name "D" :id 4 :children []}]}]}))
and need to update a submap like for example:
if :id is 2 , change :name to "Z"
resulting in an updated Atom:
{:name "A"
:id 1
:children [{:name "Z"
:id 2
:children []}
{:name "C"
:id 3
:children [{:name "D" :id 4 :children []}]}]}
how can this be done?
You could do it with postwalk or prewalk from the clojure.walk namespace.
(def x (atom {:name "A"
:id 1
:children [{:name "B"
:id 2
:children []}
{:name "C"
:id 3
:children [{:name "D" :id 4 :children []}]}]}))
(defn update-name [x]
(if (and (map? x) (= (:id x) 2))
(assoc x :name "Z")
x))
(swap! x (partial clojure.walk/postwalk update-name))
You could also use Zippers from the clojure.zip namespace
Find a working example here: https://gist.github.com/renegr/9493967
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"}}