Clojure: traversing map vectors and adding elements that repeat - clojure

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

Related

Convert a list of maps by the values of the maps [clojure]

I have a list filled with many maps (all of them have the same key), like this:
({:a 1} {:a 1} {:a 2} {:a 2} {:a 3} {:a 2})
I would like to convert it to a map that stores the occurrence of the value of each map. For exemple, the list above should return the following map:
{:1 2, :2 3, :3 1}
Any ideas on how can i do that?
(def m '({:a 1} {:a 1} {:a 2} {:a 2} {:a 3} {:a 2}))
(frequencies (map :a m)) ;; => {1 2, 2 3, 3 1}
Note the keys of the result are not keywords, as that would be an odd thing to do.
I would solve it like this:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn maps->freqs
[maps]
(frequencies
(for [m maps]
(second (first m)))))
(dotest
(let [data (quote
({:a 1} {:a 1} {:a 2} {:a 2} {:a 3} {:a 2}))]
(is= (maps->freqs data)
{1 2, 2 3, 3 1})))
The above uses my favorite template project. The best technique is to build it up slowely:
(defn maps->freqs
[maps]
(for [m maps]
(first m)))
then (spyx-pretty (maps->freqs data)) produces
(maps->freqs data) =>
[[:a 1] [:a 1] [:a 2] [:a 2] [:a 3] [:a 2]]
modify it:
(defn maps->freqs
[maps]
(for [m maps]
(second (first m))))
with result
(maps->freqs data) =>
[1 1 2 2 3 2]
Then use frequencies to get the final result.
Please be sure to read the list of documentation, especially the Clojure CheatSheet!

Intersecting a list of lists of maps overriding equality

I have a list of lists of maps:
(( {:id 1 :temp 1} {:id 2} )
( {:id 1 :temp 2} )
( {:id 1 :temp 3} {:id 2} ))
I want to get ids which are at intersection of these 3 sets only by :id key. So my result here will be 1
I came up with this solution but it's hurting my eyes:
(def coll '(( {:id 1 :temp 1} {:id 2} )
( {:id 1 :temp 2} )
( {:id 1 :temp 3} {:id 2} )))
(apply clojure.set/intersection
(map set (map (fn [m]
(map #(select-keys % '(:id)) m)) coll)))
returns
#{{:id 1}}
which is Ok, but any other suggestions?
If you are fine with getting #{1} (as you mention initially) instead of #{{:id 1}}, then it can be slightly improved:
(apply set/intersection (map (fn [c] (into #{} (map :id c))) coll))
(require '[clojure.set :refer [intersection]])
The select keys I guess you don't need, since you are only interested in the id. (map :id m) does the job for the inner-most map. By this you are getting rid of a function shorthand. You can use it in the next map:
(map #(map :id %) coll)
;; ((1 2) (1) (1 2))
The third map you introduce is not necessary. it can be merged in the above piece of code:
(map (comp set #(map :id %)) coll)
or:
(map #(set (map :id %)) coll)
both evaluating to: (#{1 2} #{1} #{1 2})
This is still pretty nested. Threading macros don't help here. But you can use a very powerful list comprehension macro called for:
(for [row coll]
(set (map :id row)))
This gives you the advantage of naming list items (rows) but keeping it concise at the same time.
So finally:
(apply intersection (for [row coll]
(set (map :id row))))
;; #{1}

How to group-by a collection that is already grouped by in Clojure?

I have a collection of maps
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
I want to group this first by value, followed by type.
My output should look like:
{
:orange [{
:value 3,
:id [9345, 145]
}, {
:value 2,
:id [2935]
}],
:apple [{
:value 6,
:id [2745, 2345]
}]
}
How would I do this in Clojure? Appreciate your answers.
Thanks!
Edit:
Here is what I had so far:
(defn by-type-key [data]
(group-by #(get % "type") data))
(reduce-kv
(fn [m k v] (assoc m k (reduce-kv
(fn [sm sk sv] (assoc sm sk (into [] (map #(:id %) sv))))
{}
(group-by :value (map #(dissoc % :type) v)))))
{}
(by-type-key a))
Output:
=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345], 3 [125]}}
I just couldnt figure out how to proceed next...
Your requirements are a bit inconsistent (or rather irregular) - you use :type values as keywords in the result, but the rest of the keywords are carried through. Maybe that's what you must do to satisfy some external formats - otherwise you need to either use the same approach as with :type through, or add a new keyword to the result, like :group or :rows and keep the original keywords intact. I will assume the former approach for the moment (but see below, I will get to the shape as you want it,) so the final shape of data is like
{:orange
{:3 [9345 145],
:2 [2945]},
:apple
{:6 [2745 2345]}
}
There is more than one way of getting there, here's the gist of one:
(group-by (juxt :type :value) a)
The result:
{["orange" 3] [{:id 9345, :value 3, :type "orange"} {:id 145, :value 3, :type "orange"}],
["orange" 2] [{:id 2945, :value 2, :type "orange"}],
["apple" 6] [{:id 2745, :value 6, :type "apple"} {:id 2345, :value 6, :type "apple"}]}
Now all rows in your collection are grouped by the keys you need. From this, you can go and get the shape you want, say to get to the shape above you can do
(reduce
(fn [m [k v]]
(let [ks (map (comp keyword str) k)]
(assoc-in m ks
(map :id v))))
{}
(group-by (juxt :type :value) a))
The basic idea is to get the rows grouped by the key sequence (and that's what group-by and juxt do,) and then combine reduce and assoc-in or update-in to beat the result into place.
To get exactly the shape you described:
(reduce
(fn [m [k v]]
(let [type (keyword (first k))
value (second k)
ids (map :id v)]
(update-in m [type]
#(conj % {:value value :id ids}))))
{}
(group-by (juxt :type :value) a))
It's a bit noisy, and it might be harder to see the forest for the trees - that's why I simplified the shape, to highlight the main idea. The more regular your shapes are, the shorter and more regular your functions become - so if you have control over it, try to make it simpler for you.
I would do the transform in two stages (using reduce):
the first to collect the values
the second for formating
The following code solves your problem:
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
(defn standardise [m]
(->> m
;; first stage
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{})
;; second stage
(reduce-kv (fn [out k v]
(assoc out (keyword k)
(reduce-kv (fn [out value id]
(conj out {:value value
:id id}))
[]
v)))
{})))
(standardise a)
;; => {:orange [{:value 3, :id [9345 145]}
;; {:value 2, :id [2945]}],
;; :apple [{:value 6, :id [2745 2345]}]}
the output of the first stage is:
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{}
a)
;;=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345]}}
You may wish to use the built-in function group-by. See http://clojuredocs.org/clojure.core/group-by

In clojure, how to reverse a map hierarchy [duplicate]

This question already has answers here:
Turn a hash map inside out in Clojure
(3 answers)
Closed 8 years ago.
In clojure, I have a map that contains for each day, and each fruit, the number of fruits eaten. I would like to "reverse the hierarchy" of the map and to return the same data but with the fruits at the top of the hierarchy.
I will explain by an example:
(map-reverse-hierarchy {:monday {:banana 2 :apple 3}
:tuesday {:banana 5 :orange 2}})
; => {:orange {:tuesday 2},
; :banana {:tuesday 5, :monday 2},
; :apple {:monday 3}}
You could use a list comprehension and some destructuring, like
user=> (->> (for [[day consum] data
#_=> [fruit amount] consum]
#_=> {fruit {day amount}})
#_=> (apply merge-with conj))
{:orange {:tuesday 2}, :banana {:tuesday 5, :monday 2}, :apple {:monday 3}}
user=>
or using a function + mapcat instead:
(defn flip [[day consum]]
(map (fn [[fruit amount]] {fruit {day amount}}) consum))
(apply merge-with conj (mapcat flip data))
My solution first transposes the pieces of the nested maps and then merges them all.
The pieces are transposed from {k1 {k2 v}}to {k2 {k1 v}} and then merged by apply merge-with conj
(defn map-reverse-hierarchy [mm]
(apply merge-with conj
(for [[k1 m] mm [k2 v] m] {k2 {k1 v}})))
Maybe:
(defn map-reverse-hierarchy [m]
(let [foo (fn [a lst]
(map #(do [(first %) {a (second %)}]) lst))
bbb (map (fn [[a b]] (into {} (foo a b))) m)]
(if (seq bbb)
(apply merge-with merge bbb)
{})))
(map-reverse-hierarchy {:monday {:banana 2 :apple 3}
:tuesday {:banana 5 :orange 2}})
;{:banana {:monday 2, :tuesday 5}, :apple {:monday 3}, :orange {:tuesday 2}}
I think you'll need some custom function. Use clojure.set/map-invert[1] to swap keys and values in hash-map
[1] http://clojure.github.io/clojure/clojure.set-api.html#clojure.set/map-invert
brute-force solution:
(defn x []
(let [i {:monday {:banana 2 :apple 3}
:tuesday {:banana 5 :orange 2}}]
(reduce-kv (fn [h day fruits]
(reduce-kv (fn [h fruit n]
(update-in h [fruit day] #(+ (or % 0) n))) h fruits)) {} i)))
user> (pprint (x))
{:orange {:tuesday 2},
:banana {:tuesday 5, :monday 2},
:apple {:monday 3}}

How to reduce a nested collection without using mutable state?

Given a nested collection I would like to reduce it to only the k-v pairs which are the form [_ D] where D is an integer. For instance I would like to transform as follows:
; Start with this ...
{:a {:val 1 :val 2} :b {:val 3 :c {:val 4}} :val 5}
; ... end with this
{:val 1, :val 2, :val 3, :val 4, :val 5}
I have written a function using postwalk as follows:
(defn mindwave-values [data]
(let [values (atom {})
integer-walk (fn [x]
(if (map? x)
(doseq [[k v] x]
(if (integer? v) (swap! values assoc k v)))
x))]
(postwalk integer-walk data)
#values))
I am curious if it is possible to do this without using mutable state?
EDIT The original function was not quite correct.
Your example data structure is not a legal map, so I've changed it a bit:
(defn int-vals [x]
(cond (map? x) (mapcat int-vals x)
(coll? x) (when (= 2 (count x))
(if (integer? (second x))
[x]
(int-vals (second x))))))
user> (int-vals {:a {:x 1 :y 2} :b {:val 3 :c {:val 4}} :val 5})
([:y 2] [:x 1] [:val 4] [:val 3] [:val 5])
Your requirements are a bit vague: you say "collection", but your example contains only maps, so I've just had to guess at what you intended.