10

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?

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
David Williams
  • 8,388
  • 23
  • 83
  • 171

5 Answers5

12
(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)
Michiel Borkent
  • 34,228
  • 15
  • 86
  • 149
3

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}
Community
  • 1
  • 1
om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
  • How about for one value? So what if I want {a: 7 :b 4}. What if I have a map of maps of integers and want to increment the value of say key1 => subkey1 => integer++? – David Williams Apr 13 '13 at 21:54
  • Got it, this creates a new map, correct? I'm thinking this makes keeping state harder. For example, to create a bayes classifier, I need to constantly keep the tallies of things updated in a multilevel structure. key1 => subkey1 => integer. Would I need to do som dosync and swap magic? – David Williams Apr 13 '13 at 22:05
  • 2
    Just be careful if you do this in a multithreaded environment (like a web application). You'll have to either use Clojure's STM or something like http://stackoverflow.com/questions/7461562/synchronized-counter-in-clojure . If your using Java or want a Java solution Guava's AtomicLongMap takes care of concurrent counters. – Adam Gent Apr 13 '13 at 22:34
2

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"}}
NielsK
  • 6,886
  • 1
  • 24
  • 46
1

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 %)))
Kannan Ramamoorthy
  • 3,980
  • 9
  • 45
  • 63
0

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}
slipset
  • 2,960
  • 2
  • 21
  • 17