Reputation: 93
I am trying to make a function in Clojure that would take a map, x and y values as parameters and then go through all the elements in a map and potentially replace all x values (if any) with y.
(defn Change-x-to-y-in-map [map x y]; code here)
For example, if I would have a map like {:a 1 :b 2 :c 1}
and I would call the function with parameters [map 1 "it works!"], the function should return the following map: {:a "it works!" :b 2 :c "it works!}
.
So it would replace all keys with value 1 to keys with value "it works!".
Thank you already beforehand for your help!
Upvotes: 1
Views: 1383
Reputation: 16035
Specter solution to boot:
(defn replace-vals [m v r]
(setval [MAP-VALS (partial = v)] r m))
Upvotes: 0
Reputation: 13473
Starting from Stefan's answer, we can modify the initial map instead of building a new one:
(defn update-v [m ov nv]
(reduce-kv (fn [acc k v] (if (= v ov) (assoc acc k nv) acc))
m m))
And we can use a transient to make the modifications to:
(defn update-v [m ov nv]
(persistent!
(reduce-kv (fn [acc k v] (if (= v ov) (assoc! acc k nv) acc))
(transient m) m)))
These changes should (hmmmmmm) speed things up a little.
(I hereby put this code under Apache 2.0 license if you can't take it under the SO default CC-BY SA)
Upvotes: 3
Reputation: 16194
You can do this generically over any form with clojure.walk
functions:
(defn replace-vals [m v r]
(walk/postwalk
(fn [e] (if (= e v) r e))
m))
(replace-vals {:a 1 :b 2 :c 1} 1 "Hey!")
=> {:a "Hey!", :b 2, :c "Hey!"}
(replace-vals [1 2 3 4 1] 1 "Hey!")
=> ["Hey!" 2 3 4 "Hey!"]
This will also work for nested forms.
(replace-vals {:a 1 :b {:c 1 :d "Bye!"}} 1 "Hey!")
=> {:a "Hey!", :b {:c "Hey!", :d "Bye!"}}
If you want to only replace map values you could refactor to this:
(defn replace-map-vals [m v r]
(walk/prewalk
(fn [e] (if (and (map-entry? e) (= (val e) v))
[(key e) r]
e))
m))
(replace-map-vals {1 "not replaced" :replaced 1} 1 "Hey!")
=> {1 "not replaced", :replaced "Hey!"})
Note this version uses prewalk
due to an issue with postwalk
and map entries.
Upvotes: 3
Reputation: 29958
The function map-vals
already exists in the Tupelo library. It applies a function tx-fn
to every value in the map, creating a new output map. The unit tests show it in action:
(let [map-123 {:a 1 :b 2 :c 3}
tx-fn {1 101 2 202 3 303}]
(is= (t/map-vals map-123 inc) {:a 2, :b 3, :c 4})
(is= (t/map-vals map-123 tx-fn) {:a 101, :b 202, :c 303}))
In the first case, the function inc
is applied to each value in the input map (IM).
The 2nd example uses a "transformation map" as tx-fn
, where each [k v] pair indicates the desired transformation from old to new values, respectivly, in IM. Thus we see values in map-123
changed as:
1 -> 101
2 -> 202
3 -> 303
A sister function map-keys
is also available:
(dotest
(let [map-123 {1 :a 2 :b 3 :c}
tx-fn {1 101 2 202 3 303}]
(is= (t/map-keys map-123 inc) { 2 :a 3 :b 4 :c})
(is= (t/map-keys map-123 tx-fn) {101 :a 202 :b 303 :c}))
Upvotes: -2
Reputation: 1665
A reducible-friendly version would use reduce-kv
directly:
(defn update-v [m ov nv]
(reduce-kv (fn [acc k v]
(assoc acc k (if (= v ov) nv v)))
{} m))
(update-v {:x 1 :y 2 :z 1} 1 "It Works!") => {:x "It Works!", :y 2, :z "It Works!"}
(I hereby put this code under Apache 2.0 license if you can't take it under the SO default CC-BY SA)
Upvotes: 2
Reputation: 10865
One way is to iterate through the keys and values of the map with for
and use into
to build up the new map:
(defn val-replace [m x y]
(into {} (for [[k v] m]
[k (if (= v x) y v)])))
> (val-replace {:x 1 :y 2 :z 1} 1 "It Works!")
{:x "It Works!", :y 2, :z "It Works!"}
> (val-replace1 {:a "" :b 4 :c ""} "" nil)
{:a nil, :b 4, :c nil}
Upvotes: 0