halfelf
halfelf

Reputation: 10107

Best way to update several values in hashmap?

I have a hash map like this:

{:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg" :do-not-split "abcdefg hijk"}

And I'd like to split some of the strings to get vectors:

; expected result
{:key1 ["aaa" "bbb" "ccc"] :key2 ["ddd" "eee"] :key3 ["fff" "ggg"] :do-not-split "abcdefg hijk"}

I use update-in three times now like the following but it seems ugly.

(-> my-hash (update-in [:key1] #(split % #"\s")) 
            (update-in [:key2] #(split % #"\s")) 
            (update-in [:key3] #(split % #"\s")))

I hope there's sth like (update-all my-hash [:key1 :key2 :key3] fn)

Upvotes: 3

Views: 893

Answers (3)

BillRobertson42
BillRobertson42

Reputation: 12883

Just map the values based on a function that makes the decision about whether to split or not.

user=> (def x {:key1 "aaa bbb ccc" 
               :key2 "ddd eee" 
               :key3 "fff ggg" 
               :do-not-split "abcdefg hijk"})
#'user/x

user=> (defn split-some [predicate [key value]] 
         (if (predicate key) 
           [key (str/split value #" ")] 
           [key value]))
#'user/split-some

user=> (into {} (map #(split-some #{:key1 :key2 :key3} %) x))
{:do-not-split "abcdefg hijk", :key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}

Upvotes: 2

leonardoborges
leonardoborges

Reputation: 5619

This is a different way of approaching the problem.

Think about it for a second: if your string were in a list, how would you approach it?

The answer is that you would use map to get a list of vectors:

(map #(split % #"\s") list-of-strings)

If you think harder you would arrive at the conclusion that what you really want is to map a function over the values of a map. Obviously map doesn't work here as it works for sequences only.

However, is there a generic version of map? It turns out there is! It's called fmap and comes from the concept of functors which you can ignore for now. This is how you would use it:

(fmap my-hash #(split % #"\s"))

See how the intent is a lot clearer now?

The only drawback is that fmap isn't a core function but it is available through the algo.generic library.

Of course if including a new library feels like too much at this stage, you can always steel the source code - and attribute to its author - from the library itself in this link:

(into (empty my-hash) (for [[k v] my-hash] [k (your-function-here v)]))

Upvotes: 0

sloth
sloth

Reputation: 101072

You can use reduce:

user=> (def my-hash {:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg"})
#'user/my-hash
user=> (defn split-it [s] (clojure.string/split s #"\s"))
#'user/split-it
user=> (reduce #(update-in %1 [%2] split-it) my-hash [:key1 :key2 :key3])
{:key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}

Upvotes: 4

Related Questions