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

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

Related

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

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.

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.

How to Increment Values in a Map

I am wrapping my head around state in Clojure. I come from languages where state can be mutated. For example, in Python, I can create a dictionary, put some string => integer pairs inside, and then walk over the dictionary and increment the values.
How would I do this in idiomatic Clojure?
(def my-map {:a 1 :b 2})
(zipmap (keys my-map) (map inc (vals my-map)))
;;=> {:b 3, :a 2}
To update only one value by key:
(update-in my-map [:b] inc) ;;=> {:a 1, :b 3}
Since Clojure 1.7 it's also possible to use update:
(update my-map :b inc)
Just produce a new map and use it:
(def m {:a 3 :b 4})
(apply merge
(map (fn [[k v]] {k (inc v) }) m))
; {:b 5, :a 4}
To update multiple values, you could also take advantage of reduce taking an already filled accumulator, and applying a function on that and every member of the following collection.
=> (reduce (fn [a k] (update-in a k inc)) {:a 1 :b 2 :c 3 :d 4} [[:a] [:c]])
{:a 2, :c 4, :b 2, :d 4}
Be aware of the keys needing to be enclosed in vectors, but you can still do multiple update-ins in nested structures like the original update in.
If you made it a generalized function, you could automatically wrap a vector over a key by testing it with coll?:
(defn multi-update-in
[m v f & args]
(reduce
(fn [acc p] (apply
(partial update-in acc (if (coll? p) p (vector p)) f)
args)) m v))
which would allow for single-level/key updates without the need for wrapping the keys in vectors
=> (multi-update-in {:a 1 :b 2 :c 3 :d 4} [:a :c] inc)
{:a 2, :c 4, :b 2, :d 4}
but still be able to do nested updates
(def people
{"keith" {:age 27 :hobby "needlefelting"}
"penelope" {:age 39 :hobby "thaiboxing"}
"brian" {:age 12 :hobby "rocket science"}})
=> (multi-update-in people [["keith" :age] ["brian" :age]] inc)
{"keith" {:age 28, :hobby "needlefelting"},
"penelope" {:age 39, :hobby "thaiboxing"},
"brian" {:age 13, :hobby "rocket science"}}
To slightly improve #Michiel Brokent's answer. This will work if the key already doesn't present.
(update my-map :a #(if (nil? %) 1 (inc %)))
I've been toying with the same idea, so I came up with:
(defn remap
"returns a function which takes a map as argument
and applies f to each value in the map"
[f]
#(into {} (map (fn [[k v]] [k (f v)]) %)))
((remap inc) {:foo 1})
;=> {:foo 2}
or
(def inc-vals (remap inc))
(inc-vals {:foo 1})
;=> {:foo 2}

swap keys and values in a map

Is there a function to swap the key and value of a given map. So given a map, I want the keys to become values, and values the keys.
(swap {:a 2 b 4}) => {2 :a 4 :b}
One way to do it is
(zipmap (vals my-map) (keys my-map))
However wondering if clojure provides a utility fn for this?
This is the purpose of map-invert in clojure.set:
user=> (clojure.set/map-invert {:a 2 :b 4})
{4 :b, 2 :a}
For anyone reading this at a later date I think the following should be helpful.
A small library is available here https://clojars.org/beoliver/map-inversions
Inverting a map may return a relation. If the map is injective (one-to-one) then the inverse will also be one-to-one. If the map (as if often the case) is many-to-one then you should use a set or vector.
#Values treated as atomic
##one-to-one
the values of the map are unique
(defn invert-one-to-one
"returns a one-to-one mapping"
[m]
(persistent! (reduce (fn [m [k v]] (assoc! m v k)) (transient {}) m)))
(def one-to-one {:a 1 :b 2 :c 3})
> (invert-one-to-one one-to-one)
{1 :a 2 :b 3 :c}
##many-to-one
The values of the map are not unique. This is very common - and it is safest to assume that your maps are of this form... so (def invert invert-many-to-one)
(defn invert-many-to-one
"returns a one-to-many mapping"
([m] (invert-many-to-one #{} m))
([to m]
(persistent!
(reduce (fn [m [k v]]
(assoc! m v (conj (get m v to) k)))
(transient {}) m))))
(def many-to-one {:a 1 :b 1 :c 2})
> (invert-many-to-one many-to-one)
{1 #{:b :a}, 2 #{:c}} ; as expected
> (invert-many-to-one [] many-to-one)
{1 [:b :a], 2 [:c]} ; we can also use vectors
> (invert-one-to-one many-to-one) ; what happens when we use the 'wrong' function?
{1 :b, 2 :c} ; we have lost information
#Values treated as collections
##one-to-many
values are sets/collections but their intersections are always empty.
(No element occurs in two different sets)
(defn invert-one-to-many
"returns a many-to-one mapping"
[m]
(persistent!
(reduce (fn [m [k vs]] (reduce (fn [m v] (assoc! m v k)) m vs))
(transient {}) m)))
(def one-to-many (invert-many-to-one many-to-one))
> one-to-many
{1 #{:b :a}, 2 #{:c}}
> (invert-one-to-many one-to-many)
{:b 1, :a 1, :c 2} ; notice that we don't need to return sets as vals
##many-to-many
values are sets/collections and there exists at least two values whose intersection is not empty. If your values are collections then it is best to assume that they fall into this category.
(defn invert-many-to-many
"returns a many-to-many mapping"
([m] (invert-many-to-many #{} m))
([to m]
(persistent!
(reduce (fn [m [k vs]]
(reduce (fn [m v] (assoc! m v (conj (get m v to) k))) m vs))
(transient {}) m))))
(def many-to-many {:a #{1 2} :b #{1 3} :c #{3 4}})
> (invert-many-to-many many-to-many)
{1 #{:b :a}, 2 #{:a}, 3 #{:c :b}, 4 #{:c}}
;; notice that there are no duplicates when we use a vector
;; this is because each key appears only once
> (invert-many-to-many [] many-to-many)
{1 [:a :b], 2 [:a], 3 [:b :c], 4 [:c]}
> (invert-many-to-one many-to-many)
{#{1 2} #{:a}, #{1 3} #{:b}, #{4 3} #{:c}}
> (invert-one-to-many many-to-many)
{1 :b, 2 :a, 3 :c, 4 :c}
> (invert-one-to-one many-to-many)
{#{1 2} :a, #{1 3} :b, #{4 3} :c} ; this would be missing information if we had another key :d mapping to say #{1 2}
You could also use invert-many-to-many on the one-to-many example.
There's a function reverse-map in clojure.contrib.datalog.util, it's implemented as:
(defn reverse-map
"Reverse the keys/values of a map"
[m]
(into {} (map (fn [[k v]] [v k]) m)))
Here is an option that may fit the problem using reduce:
(reduce #(assoc %1 (second %2) (first %2)) {} {:a 2 :b 4})
Here in a function
(defn invert [map]
(reduce #(assoc %1 (second %2) (first %2)) {} map))
Calling
(invert {:a 2 b: 4})
Then there is the reduce-kv (cleaner in my opinion)
(reduce-kv #(assoc %1 %3 %2) {} {:a 2 :b 4})