idiomatic way to update maps that match a predicate in a vector
I have a reference to a vector [] that has maps added to it. If I want to change the value of a map item based on a predicate matching, what is the idiomatic way to do that?
For example...
[ { :id 1 :name "Joe" } { :id 2 :name "Fred" } ]
And we want to update 开发者_Go百科any id of 2 to the name 'Brian'.
Here's one way:
(def people [ { :id 1 :name "Joe" } { :id 2 :name "Fred" } ])
(defn brian-converter [person]
(if (= 2 (:id person))
(assoc person :name "Brian")
person))
(map brian-converter people)
;;=> ({:id 1, :name "Joe"} {:id 2, :name "Brian"})
Depending on how you expect those values to change, you might prefer something more flexible:
(defn create-converter [[key-to-match val-to-match]
key-to-replace val-to-replace]
(fn [person]
(if (= val-to-match (key-to-match person))
(assoc person key-to-replace val-to-replace)
person)))
(map (create-converter [:id 2] :name "Brian") people)
;;=> ({:id 1, :name "Joe"} {:id 2, :name "Brian"})
(map (create-converter [:id 1] :name "Dude") people)
;;=> ({:id 1, :name "Dude"} {:id 2, :name "Fred"})
The choice of argument representation (vector for the search params, unrolled arguments for the replacements) in create-converter
was kind of arbitrary for me; not sure if there's a rule for that.
Another way is to find the index of the map you want to update, then update only that map [EDIT: this is assuming you only want to update a single item in the vector]:
(def people [{:id 1 :name "Joe"} {:id 2 :name "Fred"}])
(defn vecmap-assoc-pred
[vm pred & kvs]
(let [i (some (fn [[i m]] (when (pred m) i))
(map-indexed vector vm))]
(apply update-in vm [i] assoc kvs)))
(vecmap-assoc-pred people #(= 2 (:id %)) :name "Brian")
For this particular case, it probably makes more sense to use a map of people, keyed by id:
(def people {1 {:id 1 :name "Joe"} 2 {:id 2 :name "Fred"}})
(assoc-in people [2 :name] "Brian")
精彩评论