Update vector inside reduce - clojure

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

Related

Clojure: Create or update Map values in a vector

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.

Clojure: traversing map vectors and adding elements that repeat

I hope you are all well. Well, I am new to Clojure programming and would like some help. I have the following map vector:
(def curves [{:curve_buyer "curve1" :curve_seller "curve2" :quantity 2 :value 3}
{:curve_buyer "curve1" :curve_seller "curve3" :quantity 2 :value 4}
{:curve_buyer "curve3" :curve_seller "curve2" :quantity 2 :value 3}
{:curve_buyer "curve1" :curve_seller "curve3" :quantity 2 :value 3}
{:curve_buyer "curve1" :curve_seller "curve2" :quantity 4 :value 4}
{:curve_buyer "curve1" :curve_seller "curve3" :quantity 2 :value 4}])
I need to do a check on that vector, where it checks the keys (:curve_buyer and :curve_seller) that have the same value. If they have the same value, I need to add quantity and value, otherwise return the map as is. Taking the map above I would have the following return.
(def curves [{:curve_buyer "curve1" :curve_seller "curve2" :quantity 6 :value 7}
{:curve_buyer "curve2" :curve_seller "curve3" :quantity 2 :value 4}
{:curve_buyer "curve3" :curve_seller "curve2" :quantity 2 :value 3}
{:curve_buyer "curve1" :curve_seller "curva3" :quantity 4 :value 7}])
I need a function that does this. Any way using the clojure functions or an idea of a function that can solve my problem.
Thank you for your attention and I am willing for any clarification.
Your output is probably wrong, because map {:curve_buyer "curve2" :curve_seller "curve3" :quantity 2 :value 4} is not in curves.
This task can be solved with group-by and merge-with. First, divide maps into groups by given keys:
(def curves [{:curve_buyer "curve1" :curve_seller "curve2" :quantity 2 :value 3}
{:curve_buyer "curve1" :curve_seller "curve3" :quantity 2 :value 4}
{:curve_buyer "curve3" :curve_seller "curve2" :quantity 2 :value 3}
{:curve_buyer "curve1" :curve_seller "curve3" :quantity 2 :value 3}
{:curve_buyer "curve1" :curve_seller "curve2" :quantity 4 :value 4}
{:curve_buyer "curve1" :curve_seller "curve3" :quantity 2 :value 4}])
(->> curves
(group-by (juxt :curve_buyer :curve_seller)))
=>
{["curve1" "curve2"] [{:curve_buyer "curve1", :curve_seller "curve2", :quantity 2, :value 3}
{:curve_buyer "curve1", :curve_seller "curve2", :quantity 4, :value 4}],
["curve1" "curve3"] [{:curve_buyer "curve1", :curve_seller "curve3", :quantity 2, :value 4}
{:curve_buyer "curve1", :curve_seller "curve3", :quantity 2, :value 3}
{:curve_buyer "curve1", :curve_seller "curve3", :quantity 2, :value 4}],
["curve3" "curve2"] [{:curve_buyer "curve3", :curve_seller "curve2", :quantity 2, :value 3}]}
Then use merge-with to join maps in each group into one:
(->> curves
(group-by (juxt :curve_buyer :curve_seller))
(map (fn [[k v]] (apply merge-with
(fn [o1 o2] (if (number? o1) (+ o1 o2) o1))
v))))
=>
({:curve_buyer "curve1", :curve_seller "curve2", :quantity 6, :value 7}
{:curve_buyer "curve1", :curve_seller "curve3", :quantity 6, :value 11}
{:curve_buyer "curve3", :curve_seller "curve2", :quantity 2, :value 3})
And as a function:
(defn summarize-by-keys [keys summary-fn list-of-maps]
(->> list-of-maps
(group-by (apply juxt keys))
(map (fn [[k v]] (apply merge-with
summary-fn
v)))))
;; call it by:
(summarize-by-keys [:curve_buyer :curve_seller]
#(if (number? %1) (+ %1 %2) %1)
curves)
use reduce and map. you can make summary in first reduce. group-by looks simple, but it uses reduce for grouping, not for making summary.
(->> curves
(reduce (fn [m {:keys [buyer seller q v]}]
(update m [buyer seller]
#(-> %
(update :q (fnil + 0) q)
(update :v (fnil + 0) v))))
{})
(map (fn [[[buyer seller] m]]
(assoc m :buyer buyer :seller seller))))
also you can use map as a key
(->> curves
(reduce (fn [m {:keys [q v] :as curve}]
(update m (select-keys curve [:buyer :seller])
#(-> %
(update :q (fnil + 0) q)
(update :v (fnil + 0) v))))
{})
(map (fn [[km sm]] (merge km sm))))
so as a function
(defn summary-list
[l key-keys val-keys]
(->> (reduce (fn [acc m]
(update acc (select-keys m key-keys)
#(reduce (fn [vm vk]
(update vm vk
(fnil + 0)
(get m vk)))
% val-keys)))
{} l)
(map (partial reduce merge))))
call like this
(summary-list curves [:curve_buyer :curve_seller] [:quantity :value])
You can use reduce to go through each element of curves and create or update the corresponding element of the result in one pass.
(reduce (fn [res m]
(let [{:keys [curve_buyer curve_seller quantity value]} m
;; idx is the index of the current buyer/seller
;; in the partial result or nil
idx (first (keep-indexed (fn [i x]
(when (and (= (:curve_buyer x) curve_buyer)
(= (:curve_seller x) curve_seller))
i))
res))]
(if idx
(update res
idx
#(assoc %
:quantity (+ quantity (:quantity %))
:value (+ value (:value %))))
(conj res m))))
[]
curves
The final result is:
[{:curve_buyer "curve1",
:curve_seller "curve2",
:quantity 6,
:value 7}
{:curve_buyer "curve1",
:curve_seller "curve3",
:quantity 6,
:value 11}
{:curve_buyer "curve3",
:curve_seller "curve2",
:quantity 2,
:value 3}]
Note that vectors can be treated as associative collections, with the indices of the elements to be the keys.
(require '[net.cgrand.xforms :as x])
(->> curves
(group-by (juxt :curve_buyer :curve_seller))
(map last)
(map (fn [xs] (into (first xs)
(x/multiplex
{:quantity (comp (map :quantity) (x/reduce +))
:value (comp (map :value) (x/reduce +))}) xs))))
you can also do it this way:
first we can think of the function to make a map out of every item, containing index key and values to summarize:
(defn mappify [ks vs item]
{(select-keys item ks) (select-keys item vs)})
(mappify [:curve_buyer :curve_seller]
[:quantity :value]
(first curves))
;;=>{{:curve_buyer "curve1", :curve_seller "curve2"}
;; {:quantity 2, :value 3}}
then you can mappify all the items, and merge them:
(->> curves
(map #(mappify [:curve_buyer :curve_seller]
[:quantity :value] %))
(apply merge-with (partial merge-with +))
(map (partial apply into)))
;;({:curve_buyer "curve1",
;; :curve_seller "curve2",
;; :quantity 6,
;; :value 7}
;;{:curve_buyer "curve1",
;; :curve_seller "curve3",
;; :quantity 6,
;; :value 11}
;;{:curve_buyer "curve3",
;; :curve_seller "curve2",
;; :quantity 2,
;; :value 3})

Clojure: round bigInt in map

I need some help with maps. After I get data with jdbc/query from a database, the result looks like this:
({:product_id 1, :name product1, :rating 3.000M}
{:product_id 2, :name product2, :rating 1.333M}
{:product_id 3, :name product3}, :rating nil)
I want to display everything with Selmer, but I just want only 1 number after the comma. Something like this:
({:product_id 1, :name product1, :rating 3.0}
{:product_id 2, :name product2, :rating 1.3}
{:product_id 3, :name product3}, :rating nil)
I found out, how to iterate over a map, but i dont know how to change the specific value. The query data is saved in data
(doseq [keyval data]
(doseq [keyval2 keyval]
(doseq [keyval3 keyval2]
(prn keyval3))))
Can you help me create a new data variable. Thanks!
clojure data is immutable, so you can't update it in a common way. Rather you make the copy of your data adding the needed changes using clojure's data manipulation functions. Nice introduction can be found here: http://www.braveclojure.com/functional-programming/
so what you do is something like this
user> (defn round-to [^Double num places] (when num (Math/round num)))
#'user/round-to ;; not-a-real-round-to (simplified for brevity)
user> (def data '({:product_id 1, :name product1, :rating 3.000M}
{:product_id 2, :name product2, :rating 1.333M}
{:product_id 3, :name product3, :rating nil}))
#'user/data
user> (map #(update % :rating round-to 2) data)
;;=> ({:product_id 1, :name product1, :rating 3}
;; {:product_id 2, :name product2, :rating 1}
;; {:product_id 3, :name product3, :rating nil})
(defn round2
"Round a double to the given precision (number of significant digits)"
[precision d]
(let [factor (Math/pow 10 precision)]
(/ (Math/round (* d factor)) factor)))
(map #(assoc % :rating (when-some [r (:rating %)] (round2 1 r)))
'({:product_id 1, :name 'product1, :rating 3.000M}
{:product_id 2, :name 'product2, :rating 1.333M}
{:product_id 3, :name 'product3, :rating nil}))

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.

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)