Clojure: How do I apply a function to a subset of the entries in a hash-map?

后端 未结 3 1148
鱼传尺愫
鱼传尺愫 2021-02-06 12:43

I am not to Clojure and attempting to figure out how to do this.

I want to create a new hash-map that for a subset of the keys in the hash-map applies a function to the

相关标签:
3条回答
  • 2021-02-06 13:07
    (defn doto-map [m ks f & args]
      (reduce #(apply update-in %1 [%2] f args) m ks))
    

    Example call

    user=> (doto-map {:a 1 :b 2 :c 3} [:a :c] + 2)
    {:a 3, :b 2, :c 5}
    

    Hopes this helps.

    0 讨论(0)
  • 2021-02-06 13:09

    The following seems to work:

    (defn doto-map [ks f amap]
      (into amap
        (map (fn [[k v]] [k (f v)])
             (filter (fn [[k v]] (ks k)) amap))))
    
    user=> (doto-map #{:hello :foo} (fn [k] (.toUpperCase k)) {:hello "World" :try "This" :foo "bar"})
    {:hello "WORLD", :try "This", :foo "BAR"}
    

    There might be a better way to do this. Perhaps someone can come up with a nice one-liner :)

    0 讨论(0)
  • 2021-02-06 13:25
    (defn do-to-map [amap keyseq f]
      (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq))

    Breakdown:

    It helps to look at it inside-out. In Clojure, hash-maps act like functions; if you call them like a function with a key as an argument, the value associated with that key is returned. So given a single key, the current value for that key can be obtained via:

    (some-map some-key)
    

    We want to take old values, and change them to new values by calling some function f on them. So given a single key, the new value will be:

    (f (some-map some-key))
    

    We want to associate this new value with this key in our hash-map, "replacing" the old value. This is what assoc does:

    (assoc some-map some-key (f (some-map some-key)))
    

    ("Replace" is in scare-quotes because we're not mutating a single hash-map object; we're returning new, immutable, altered hash-map objects each time we call assoc. This is still fast and efficient in Clojure because hash-maps are persistent and share structure when you assoc them.)

    We need to repeatedly assoc new values onto our map, one key at a time. So we need some kind of looping construct. What we want is to start with our original hash-map and a single key, and then "update" the value for that key. Then we take that new hash-map and the next key, and "update" the value for that next key. And we repeat this for every key, one at a time, and finally return the hash-map we've "accumulated". This is what reduce does.

    • The first argument to reduce is a function that takes two arguments: an "accumulator" value, which is the value we keep "updating" over and over; and a single argument used in one iteration to do some of the accumulating.
    • The second argument to reduce is the initial value passed as the first argument to this fn.
    • The third argument to reduce is a collection of arguments to be passed as the second argument to this fn, one at a time.

    So:

    (reduce fn-to-update-values-in-our-map 
            initial-value-of-our-map 
            collection-of-keys)
    

    fn-to-update-values-in-our-map is just the assoc statement from above, wrapped in an anonymous function:

    (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
    

    So plugging it into reduce:

    (reduce (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
            amap
            keyseq)
    

    In Clojure, there's a shorthand for writing anonymous functions: #(...) is an anonymous fn consisting of a single form, in which %1 is bound to the first argument to the anonymous function, %2 to the second, etc. So our fn from above can be written equivalently as:

    #(assoc %1 %2 (f (%1 %2)))
    

    This gives us:

    (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq)
    
    0 讨论(0)
提交回复
热议问题