Arthur Camara
Arthur Camara

Reputation: 577

Clojure: iterate over map of sets

This is pretty much a follow-up to my last question (Clojure idiomatic way to update multiple values of map), but not quite the same. (keep in mind that I'm fairly new to Clojure and functional languages alike)

suppose I have the following data structure, defined as a map of sets:

(def m1 {:1 #{2} :2 #{1 3} :3 #{1}})

and a map of maps as such:

(def m2 {:1 {:1 0 :2 12 :3 23} :2 {:1 23 :2 0 :3 4} :3 {:1 2 :2 4 :3 0}})

What I want to do is update the registries of m2 that have a correspondence in m1 to a certain value. Let's say the value I want is x. The resulting m2 would be something like this:

{:1 {:1 0 :2 x :3 23} :2 {:1 x :2 0 :3 x} :3 {:1 x :2 4 :3 0}}

Assuming that v contains every possible key for my map, y first attempt, (that I failed miserably) is to do something like this: (assume that x=1

(for [i v]
 reduce (fn [m j] (assoc-in m [i j] 1)) d (i m1)))

needless to say that it was a failure. So, how is the idiomatic way to do this?

Upvotes: 3

Views: 2142

Answers (3)

Ankur
Ankur

Reputation: 33637

Try this (here x is 100)

(merge-with merge m2 
   (into {} (for [[k v] m1] [k (into {} (for [i v] [(keyword (str i)) 100]))])))

EDIT:

The idea is this:

  • Convert m1 from {:1 #{2} :2 #{1 3} :3 #{1}} to {:1 {:2 x} :2 {:1 x :3 x} :3 {:1 x}} which is basically converting each set to a map where key is the values of the set and values are the constant x.
  • Merge both the m2 and new m1.

NOTE: There is an assumption that all the keys in m1 are there in m2.

Upvotes: 1

Magos
Magos

Reputation: 3014

As I understand your requirements you want to

  1. Produce a number of key-sequences from m1.
  2. In m2 associate each of those key-sequences with a particular constant value.

The first step is a fairly simple transformation of m1. We want to produce 0 or more key-seqs for each entry (depending on how many are in its set), so the natural go-to for me is mapcat. It stands for map-then-concat and is great for just such a case where from each element of a seq we produce 0 or more of the elements we want.

(defn key-seqs [coll]
  (mapcat 
   (fn [[k v]] 
     (map (partial vector k) v))
   coll))

(key-seqs m1)
;;=> ([:1 2] [:2 1] [:2 3] [:3 1])

Note that the function taken by mapcat is itself a map, so that it produces a sequence (possibly empty) for mapcat to unroll. But this is returning the longs stored in the sets as themselves. If we want to turn them into keywords to match m2 we need a little more processing:

(defn key-seqs [coll]
  (mapcat 
   (fn [[k v]] 
     (map (comp (partial vector k) keyword str) v))
   coll))

(key-seqs m1)
;;=> ([:1 :2] [:2 :1] [:2 :3] [:3 :1])

(We need to use str because keyword doesn't know what to do with longs. Normally keywords are not numbers, but names with some symbolic meaning)

Then we can adapt the update-m from your previous question a little bit so that it can take the constant value as an argument and handle key-sequences that don't just have the same value twice:

(defn update-m [m x v]
  (reduce (fn [m' key-seq]
            (assoc-in m' key-seq x)) ;; accumulate changes
          m   ;; initial-value
          v)) ;; collection to loop over

and now we seem to be in business:

(update-m m2 1 (key-seqs m1))
;;=> {:1 {:1 0, :2 1, :3 23}, :2 {:1 1, :2 0, :3 1}, :3 {:1 1, :2 4, :3 0}}

Upvotes: 3

user5187212
user5187212

Reputation: 426

I think a nice solution would be, if you change the data structure of m1 to something like

(def m1-new [[:1 :2] [:2 :1] [:2 :3] [:3 :1]])

Then you can just reduce over it and use assoc-in.

(reduce (fn [m path] (assoc-in m path my-new-value)) m2 m1-new)

Upvotes: 2

Related Questions