How to Increment Values in a Map - clojure

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}

Related

Clojure sum maps with strings and numbers

Hi l have 2 maps like these (with the possibility of having multiple maps)
(def map1 {:a {:b 1} :c ["dog"]})
(def map2 {:a {:b 2} :c ["cat"]})
l need to have this as a return {:a {:b 3} :c ["dog" "cat"]}
How can l do this?
Solution using generic functions
You could use generic functions - let's call it here o+ (operator +) - by making use of Clojure's multiple dispatch ability. By multiple dispatch you avoid if - else or cond clauses - for distinguishing the argument types/classes - thereby the code stays extensible (further cases can be added without having to change existing code). The function at the end of the defmulti definition is the dispatch function. In the defmethod form - right before the actual function arguments list (vector), you list the dispatch case for which then Clojure looks when deciding which method to use.
(defmulti o+ (fn [& args] (mapv class args)))
(defmethod o+ [Number Number] [x y] (+ x y)) ;; Numbers get added
(defmethod o+ [clojure.lang.PersistentVector clojure.lang.PersistentVector]
[x y] (into (empty x) (concat x y))) ;; Vectors concatentated
(defmethod o+ [clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap]
[x y] (merge-with o+ x y)) ;; Maps merged-with #'o+ (recursive definition!)
Test
You can fuse then two maps with the (now recursively defined) o+ operator:
(def map1 {:a {:b 1} :c ["dog"]})
(def map2 {:a {:b 2} :c ["cat"]})
(o+ map1 map2) ;; or originally: (merge-with o+ map1 map2)
;; => {:a {:b 3}, :c ["dog" "cat"]}
Even deeper nested maps are covered
Due to the recursive character of this definition - this works also with more deeply nested maps as long as they have the same structure - and use only the defined class cases (otherwise one could add further cases):
(def mapA {:a {:b 2 :c {:d 1 :e ["a"] :f 3}} :g ["b"]})
(def mapB {:a {:b 3 :c {:d 4 :e ["b" "e"] :f 5}} :g ["c"]})
(o+ mapA mapB)
;;=> {:a {:b 5, :c {:d 5, :e ["a" "b" "e"], :f 8}}, :g ["b" "c"]}
Use reduce to add more maps at once
And as long as you can apply o+ on two objects, you can process an arbitrary number of maps using reduce:
(def mapA {:a {:b 2 :c {:d 1 :e ["a"] :f 3}} :g ["b"]})
(def mapB {:a {:b 3 :c {:d 4 :e ["b" "e"] :f 5}} :g ["c"]})
(def mapC {:a {:b 1 :c {:d 3 :e ["c"] :f 1}} :g ["a"]})
(reduce o+ [mapA mapB mapC]) ;; this vector could contain much more maps!
;; => {:a {:b 6, :c {:d 8, :e ["a" "b" "e" "c"], :f 9}}, :g ["b" "c" "a"]}
;; or we could define in addition:
(defmethod o+ :default [& args] (reduce o+ args))
;; which makes `o+` to a variadic function (function which can be
;; called with as many arguments you want)
;; then, whenever we add more than two arguments, it will be activated:
(o+ mapA mapB mapC)
;; => {:a {:b 6, :c {:d 8, :e ["a" "b" "e" "c"], :f 9}}, :g ["b" "c" "a"]}
;; and this also works:
(o+ 1 2 3 4 5 6)
;; => 21
(o+ ["a"] ["b" "c"] ["d"])
;; => ["a" "b" "c" "d"]
You can use merge-with with a custom function to resolve conflicts when merging maps.
(defn my-merge [a b]
(merge-with (fn [a b]
(if (map? a)
(merge-with + a b)
(into a b)))
a b))
Or defined recursively:
(defn my-merge [a b]
(cond (vector? a) (into a b)
(number? a) (+ a b)
(map? a) (merge-with my-merge a b)
:else (throw (ex-info "Unsupported values" {:values [a b]}))))
Here's an idea. I didn't test this, and I'm quite sure a more clever solution will come... especially now that I said that this is an answer (the someone-said-something-wrong-on-the-internet effect) :)
(reduce
(fn [accum item]
(let [b-accum (get-in accum [:a :b])
b-item (get-in item [:a :b])
new-b (+ b-accum b-item)
new-c (vec (concat (:c accum) (:c item))]
{:a {:b new-b} :c new-c}))
[map1 map2])
(require '[net.cgrand.xforms :as x])
(let [map1 {:a {:b 1} :c ["dog"]}
map2 {:a {:b 2} :c ["cat"]}]
(->> [map1 map2]
(into {}
(x/multiplex
{:a (comp (map :a) (map :b) (x/reduce +) (x/into [:b]) (x/into {}))
:c (comp (mapcat :c) (x/reduce conj))}))))

Clojure - Count occurences of nested key in nested map?

I have a nested map like so:
{:A {:B {:A {:B {:A {:B 0}}}}}}
I want to count the occurrences of the pair [:A :B] so that in the case above, the result is 3.
My initial idea was to use clojure.walk/postwalk to traverse the map and increment a counter.
Is there a more optimal way of achieving this?
tree-seq works out nice in this case:
(defn count-ab [data]
(->> data
(tree-seq coll? seq)
(keep #(get-in % [:A :B]))
count))
user> (def data {:A {:B {:A {:B {:A {:B 0}}}}}})
#'user/data
user> (count-ab data)
3
user> (def data-1 {:A {:B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-1
user> (count-ab data-1)
3
user> (def data-2 {:A {:X {:A {:B 100}}
:B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-2
user> (count-ab data-2)
4
Because it's nested map, my pragmatic idea is to traverse recursively and count:
(defn do-count [m]
(loop [m m
counter 0]
(if-let [m* (get-in m [:A :B])]
(recur m* (inc counter))
counter)))
(do-count {:A {:B {:A {:B {:A {:B 0}}}}}}) ;==> 3
I would suggest writing a function to turn the deeply nested map into a flat map keyed by paths
(defn flatten-map
"unwind deeply nested map into map of paths and vals"
([m] (into {} (flatten-map [] m)))
([path m]
(if (map? m)
(mapcat (fn [[k v]]
(flatten-map (conj path k) v))
m)
[[path m]])))
you can use this to count the adjacent [:a :b] keys like this
(->> {:A {:B {:A {:B {:A {:B 0}}}}}}
flatten-map
keys
(mapcat #(partition 2 1 %))
(filter #(= [:A :B] %))
count)

aggregate map values into a vector

I'm wondering if anyone can help me find the right function to use with merge-with to get the desired merging of map values as a single vector.
Thanks!
; works great -single vector
(merge-with vector {:a "b"} {:a "d"} {:a "c"})
; {:a ["b" "d"]}
; uh-oh... now we are beginning to nest each set
(merge-with vector {:a "b"} {:a "d"} {:a "c"})
;{:a [["b" "d"] "c"]}
; what I want:
; {:a ["b" "d" "c"]}
though the approach with flatten solves your concrete problem, it is not universal. Based on your question i would guess that you need a map of keyword to vector as a result. And it works, when all the maps contain exactly same keys. But guess the following corner cases:
user> (merge-with (comp flatten vector) {:a "b"})
;;=> {:a "b"} oops! you following processing probably wants {:a ["b"]}
user> (merge-with (comp flatten vector) {:a "b"} {:c "d"})
;;=> {:a "b", :c "d"} once again!
user> (merge-with (comp flatten vector) {:a ["b"]} {:a ["c" ["d"]]})
;;=> {:a ("b" "c" "d")}
;; here i can see some inconsistent behavior, breaking the initial data form: would't you rather want {:a [["b"] ["c" ["d"]]]} ?
so, given that you are doing something for production, rather then learning,
i would advice the following approach: you can make the function, merging maps, but also handling the single (or first) key appearing in the result the special way:
(defn smart-merge-with [first-val-fn merge-fn & args]
(when (seq args)
(reduce (fn [acc items-map]
(reduce (fn [acc [k v]]
(if (contains? acc k)
(update acc k merge-fn v)
(assoc acc k (first-val-fn v))))
acc items-map))
{} args)))
now you can just wrap the first value into a vector, and then, when there is another value with the same key appears just add it to that vector:
user> (smart-merge-with vector conj {:a 10 :b 30} {:a 20 :c 30} {:c 1} {:d 100})
;;=> {:a [10 20], :b [30], :c [30 1], :d [100]}
user> (smart-merge-with vector conj {:a [10] :b 30} {:a 20 :c 30} {:c 1} {:d 100})
{:a [[10] 20], :b [30], :c [30 1], :d [100]}
in addition, now you can add more sophisticated logic to the maps' merging, like for example some accumulation:
user> (smart-merge-with (fn [x] {:items [x] :sum x})
(fn [x y] (-> x
(update :items conj y)
(update :sum + y)))
{:a 10 :b 20} {:b 30 :c 40} {:c 1 :d 2})
;;=> {:a {:items [10], :sum 10},
;; :b {:items [20 30], :sum 50},
;; :c {:items [40 1], :sum 41},
;; :d {:items [2], :sum 2}}
From this answer we can use the same principle:
(merge-with (comp #(into [] % ) flatten vector) {:a "b"} {:a "d"} {:a "c"})
{:a ["b" "d" "c"]}
Or roll you own function:
(merge-with #(if (vector? %1) (conj %1 %2) (vector %1 %2)) {:a "b"} {:a "d"} {:a "c"})

How to select keys in nested maps in Clojure?

Let's say I have a map (m) like this:
(def m {:a 1 :b 2 :c {:d 3 :e 4} :e { ... } ....})
I'd like to create a new map only containing :a, :b and :d from m, i.e. the result should be:
{:a 1 :b 2 :d 3}
I know that I can use select-keys to easily get :a and :b:
(select-keys m [:a :b])
But what's a good way to also get :d? I'm looking for something like this:
(select-keys* m [:a :b [:c :d]])
Does such a function exists in Clojure or what's the recommended approach?
In pure Clojure I would do it like this:
(defn select-keys* [m paths]
(into {} (map (fn [p]
[(last p) (get-in m p)]))
paths))
(select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3}
I prefer keeping the type of a path regular, so a sequence of keys for all paths. In clojure.spec this would read as
(s/def ::nested-map (s/map-of keyword?
(s/or :num number? :map ::nested-map)))
(s/def ::path (s/coll-of keyword?))
(s/fdef select-keys*
:args (s/cat :m ::nested-map
:paths (s/coll-of ::path)))
As an alternative you can use destructing on a function, for example:
(def m {:a 1 :b 2 :c {:d 3 :e 4}})
(defn get-m
[{a :a b :b {d :d} :c}]
{:a 1 :b b :d d})
(get-m m) ; => {:a 1, :b 2, :d 3}
You can use clojure.walk.
(require '[clojure.walk :as w])
(defn nested-select-keys
[map keyseq]
(w/postwalk (fn [x]
(if (map? x)
(select-keys x keyseq)
(identity x))) map))
(nested-select-keys {:a 1 :b {:c 2 :d 3}} [:a :b :c])
; => {:a 1, :b {:c 2}}
I'm not aware of such a function being part of Clojure. You'll probably have to write it yourself. I've came up with this :
(defn select-keys* [m v]
(reduce
(fn [aggregate next]
(let [key-value (if (vector? next)
[(last next)
(get-in m next)]
[next
(get m next)])]
(apply assoc aggregate key-value)))
{}
v))
Require paths to be vectors so you can use peek (much faster than last). Reduce over the paths like this:
(defn select-keys* [m paths]
(reduce (fn [r p] (assoc r (peek p) (get-in m p))) {} paths))
(select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3}
Of course, this assumes that all your terminal keys are unique.

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