Clojure: Create or update Map values in a vector - clojure

I want to update some values in my vector.
To change this :
[{:key 0, :values {:value1 1, :value2 100}} {:key 1, :values {:value1 5, :value2 300}}]
In this:
[{:key 0, :values {:value1 1, :value2 100}} {:key 1, :values {:value1 6, :value2 500}}]
I'm trying to do something like that, without success:
(if (contains? myvectors myId)
(do
(assoc ((myvectors myId) :values) :value2 500));not working
)
(do
(def myvectors (merge myvectors {:key myId :values {:value1 1 :value2 300}}))
)
)

This would work:
(def v
[{:key 0 :values {:value1 1 :value2 100}} {:key 1 :values {:value1 5, :value2 300}}])
(defn update-values [v k value1 value2]
(mapv (fn [{:keys [key] :as m}]
(cond-> m
(= key k) (assoc :values {:value1 value1 :value2 value2})))
v))
(update-values v 1 6 500)
;; => [{:key 0 :values {:value1 1 :value2 100}}
;; {:key 1 :values {:value1 6 :value2 500}}]
If possible I would change your data structure so you can make better use of core functions, for example a map from key to values:
(def m
{0 {:value1 1 :value2 100}
1 {:value1 5 :value2 300}})
(assoc m 1 {:value1 6 :value2 500})
;; => {0 {:value1 1 :value2 100}
;; 1 {:value1 6 :value2 500}}

If all you want to do is change the second element (map) in your vector as per your before/after examples, you can do something like this:
(let [m [{:key 0, :values {:value1 1, :value2 100}}
{:key 1, :values {:value1 5, :value2 300}}]]
(update-in m [1 :values] merge {:value1 6 :value2 500}))
=> [{:key 0, :values {:value1 1, :value2 100}} {:key 1, :values {:value1 6, :value2 500}}]
where update-in updates the second element in the vector (index 1 in the [1 ...] expression - 0 based indexing) and on that element updates the :values key. For that value ({:value1 5, :value2 300}) we call the merge function. update-in will send in the old value (again, the old value is at this point {:value1 5, :value2 300}) as the first argument to merge and then we send in {:value1 6 :value2 500} as the second argument. Since later arguments to merge "win", the map values we send in will overwrite the ones already in your data.

It's not clear what's your goal, but I've tried to come up with something general enough:
(def data [{:key 0, :values {:value1 1, :value2 100}}
{:key 1, :values {:value1 5, :value2 300}}])
(reduce
(fn [v {:keys [key] :as row}]
(conj v
(cond-> row
(= key 1)
(assoc :values {:value1 6, :value2 500}))))
[]
data)
=> [{:key 0, :values {:value1 1, :value2 100}} {:key 1, :values {:value1 6, :value2 500}}]
This will reduce over the input sequence by changing the given row only based on some condition. Since it looks like you want to change based on the key I've added the condition (= key 1) but this can be easily adapted to any condition for anything that's contained in the row.

Related

Update vector inside reduce

Given a vector:
(def vec [{:key 1, :value 10, :other "bla"}, {:key 2, :value 13, :other "bla"}, {:key 1, :value 7, :other "bla"}])
I'd like to iterate over each element and update :value with the sum of all :values to that point, so I would have:
[{:key 1, :value 10, :other "bla"}, {:key 2, :value 23, :other "bla"}, {:key 1, :value 30, :other "bla"}])
I've found this for printing the result, but I've tried to change the prn command to update-in, assoc-in in the code below (extracted from the link above) but I didn't work quite well.
(reduce (fn [total {:keys [key value]}]
(let [total (+ total value)]
(prn key total)
total))
0 vec)
I'm new to Clojure, how can I make it work?
If you want to get the running totals then the simplest way is to use reductions:
(reductions (fn [acc ele] (+ acc (:value ele)))
0
[{:key 1, :value 10, :other "bla"}, {:key 2, :value 13, :other "bla"}, {:key 1, :value 7, :other "bla"}])
;; => (0 10 23 30)
As you can see the function you pass to reductions has the same signature as the function you pass to a reduce. It is like you are asking for a reduce to be done every time a new element is reached. Another way of thinking about it is that every time a new accumulator is calculated it is kept, unlike with reduce where the caller only gets to see the result of the last calculation.
And so this is the code that would directly answer your question:
(->> [{:key 1, :value 10, :other "bla"}, {:key 2, :value 13, :other "bla"}, {:key 1, :value 7, :other "bla"}]
(reductions #(update %2 :value + (:value %1))
{:value 0})
next
vec)
;; => [{:key 1, :value 10, :other "bla"} {:key 2, :value 23, :other "bla"} {:key 1, :value 30, :other "bla"}]
You can accumulate the :values thus:
(reductions + (map :value v))
=> (10 23 30)
(I renamed the vector v to avoid tripping over clojure.core/vec.)
Then you can use mapv over assoc:
(let [value-sums (reductions + (map :value v))]
(mapv #(assoc %1 :value %2) v value-sums))
=> [{:key 1, :value 10, :other "bla"} {:key 2, :value 23, :other "bla"} {:key 1, :value 30, :other "bla"}]

Clojure - update data values in array of hashes

I would like to update values in hashes, but I'm not sure how this can be done efficiently
I tried using a loop approach, but keeping the previous record's value also in account seems like a big challenge.
This is what I am trying to do,
Considering the records are sorted based on created_at in descending order, For example,
[{:id 1, :created_at "2016-08-30 11:07:00"}{:id 2, :created_at "2016-08-30 11:05:00"}...]
]
; Basically in humanised form.
Could anyone share some ideas to achieve this? Thanks in advance.
Simplified example:
(def data [{:value 10} {:value 8} {:value 3}])
(conj
(mapv
(fn [[m1 m2]] (assoc m1 :difference (- (:value m1) (:value m2))))
(partition 2 1 data))
(last data))
;;=> [{:value 10, :difference 2} {:value 8, :difference 5} {:value 3}]
what you need, is to iterate over all the pairs of consecutive records, keeping the first of them, adding the difference to it.
first some utility functions for dates handling:
(defn parse-date [date-str]
(.parse (java.text.SimpleDateFormat. "yyyy-MM-dd HH:mm:ss") date-str))
(defn dates-diff [date-str1 date-str2]
(- (.getTime (parse-date date-str1))
(.getTime (parse-date date-str2))))
then the mapping part:
user> (def data [{:id 1, :created_at "2016-08-30 11:07:00"}
{:id 2, :created_at "2016-08-30 11:05:00"}
{:id 3, :created_at "2016-08-30 10:25:00"}])
user> (map (fn [[rec1 rec2]]
(assoc rec1 :difference
(dates-diff (:created_at rec1)
(:created_at rec2))))
(partition 2 1 data))
({:id 1, :created_at "2016-08-30 11:07:00", :difference 120000}
{:id2, :created_at "2016-08-30 11:05:00", :difference 2400000})
notice that it doesn't contain the last item, since it was never the first item of a pair. So you would have to add it manually:
user> (conj (mapv (fn [[rec1 rec2]]
(assoc rec1 :difference
(dates-diff (:created_at rec1)
(:created_at rec2))))
(partition 2 1 data))
(assoc (last data) :difference ""))
[{:id 1, :created_at "2016-08-30 11:07:00", :difference 120000}
{:id 2, :created_at "2016-08-30 11:05:00", :difference 2400000}
{:id 3, :created_at "2016-08-30 10:25:00", :difference ""}]
now it's ok. The only difference with your desired variant, is that the diff is in millis, rather than formatted string. To do that you can add the formatting to the dates-diff function.

use #() instead of (fn ...) in (sorted-map-by ...)

I would like to translate the inner-function call in the following snippet, to one using the #() macro :
(let [m {:a 3, :b 2, :c 4, :x 9, :y 0, :z 5}]
(into (sorted-map-by (fn [key1 key2]
(compare [(get m key2)]
[(get m key1)]))) m))
I am a little bit confused on how I can accomplish that.
Inside an anonymous function, the arguments are given by %1, %2... so you can use
(let [m {:a 3, :b 2, :c 4, :x 9, :y 0, :z 5}]
(into (sorted-map-by #(compare (get m %2)
(get m %1))) m))
note you don't need to wrap the compared values in a vector.

Getting values from nested maps

I have a nested map,how can i get values of all :kvota keywords?the result should be - 5.8,3.2,2.25.I tried using select-keys but without any luck...
{:b4f0d011-31a2-4be3-bb8d-037725310207 {:tiket {:3 {:id 13, :par Porto - Zenit, :igra 2, :kvota 5.8}, :2 {:id 12, :par Celtic - Ajax, :igra x, :kvota 3.2}, :1 {:id 11, :par Arsenal - Dortmund, :igra 1, :kvota 2.25}}}}
This will get the values corresponding to each :kvota anywhere in the data structure.
;; Data in quesiton doesn't read as-is, so this is altered slightly.
(def data
{:b4f0d011-31a2-4be3-bb8d-037725310207
{:tiket
{:1 {:kvota 2.25, :par "Arsenal - Dortmund", :igra 1, :id 11}
:3 {:kvota 5.8, :par "Porto - Zenit", :igra 2, :id 13}
:2 {:kvota 3.2, :par "Celtic - Ajax", :igra "x", :id 12}}}})
(keep :kvota (tree-seq map? vals data)) ; (2.25 5.8 3.2)

Calculating a value and adding it to a map

I have a list of maps:
(def mylist
[{:id 1 :sub [{:subid 1} {:subid 2}]}
{:id 2 :sub [{:subid 3}]}])
I want to add a new key/value pair to each map element of the list that contains the count of items in :sub:
[{:id 1 :sub [{:subid 1} {:subid 2}] :subcount 2}
{:id 2 :sub [{:subid 3}] :subcount 1}]
How can I do this?
In clojure, "adding to a map" is done with assoc, which returns a new map with the specified value(s) added, and usually if you want to do the same operation on a collection of things, you use the map function.
(defn subcount
"return the number of items in the :sub of m"
[m]
(count (:sub m)))
(defn add-count
"add subcount to the given map"
[m]
(assoc m :subcount (subcount m)))
(defn add-counts
"add subcount to all the objects"
[objects]
(map add-count objects))
(def mylist
[{:id 1 :sub [{:subid 1} {:subid 2}]}
{:id 2 :sub [{:subid 3}]}])
(add-counts mylist)
=> ({:sub [{:subid 1} {:subid 2}], :subcount 2, :id 1} {:sub [{:subid 3}], :subcount 1, :id 2})