tags:

views:

104

answers:

3

Given:

(def my-vec [{:id 0 :a "foo" :b "bar"} {:id 1 :a "baz" :b "spam"} 
             {:id 2 :a "qux" :b "fred"}])

How can I idiomatically update* the item in my-vec with :id=1 to have values :a="baz2" and :b="spam2"?

*(I recognize that I wouldn't actually be updating my-vec, but really returning a new vector that is identical to my-vec except for the replacement values.)

TIA! Sean

+2  A: 

map a function over the vector of maps that either creates a modified map if the key matches or uses the original if the keys don't match then turn the result back into a vector

(vec (map #(if (= (:id %) 1) 
             (assoc % :a "baz2" :b "spam2")
             %)))

It is possible to do this more succinctly though this one really shows where the structural sharing occurs.

Arthur Ulfeldt
Does using the #(%) function syntax make this less clear?
Arthur Ulfeldt
+5  A: 

Do you know ahead of time that the map with id == 1 is the second map in your vector? If so:

user> (-> my-vec
          (assoc-in [1 :a] "baz2")
          (assoc-in [1 :b] "spam2"))
[{:id 0, :a "foo", :b "bar"} {:id 1, :a "baz2", :b "spam2"} {:id 2, :a "qux", :b "fred"}]

If you need to access your data by id a lot, another idea is to replace your vector of hash-maps with a hash-map of hash-maps keyed on :id. Then you can more easily assoc-in no matter the order of things.

user> (def new-my-vec (zipmap (map :id my-vec) my-vec))
#'user/new-my-vec
user> new-my-vec
{2 {:id 2, :a "qux", :b "fred"}, 1 {:id 1, :a "baz", :b "spam"}, 0 {:id 0, :a "foo", :b "bar"}}
user> (-> new-my-vec
          (assoc-in [1 :a] "baz2")
          (assoc-in [1 :b] "spam2"))
{2 {:id 2, :a "qux", :b "fred"}, 1 {:id 1, :a "baz2", :b "spam2"}, 0 {:id 0, :a "foo", :b "bar"}}
Brian Carper
Hmm. I store it in a vector because what I'm actually holding is ImageIcons of playing cards and the vector position winds up acting as a z-order when I draw. [{:id "8H" :image ImageIcon} ...]. But I might be able to store them in a map {{:id "8H" :z-order 2 :image ImageIcon}...} and then Graphics.draw them by pulling them out by their sorted :z-order. Thanks, Brian, you're a Clojure monster. :)
scrotty
+1  A: 

Might want to take a look at array-map which creates a map backed by an array and keyed by the index instead of using :id?

Alex Miller