Reputation: 577
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
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:
{: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.NOTE: There is an assumption that all the keys in m1 are there in m2.
Upvotes: 1
Reputation: 3014
As I understand your requirements you want to
m1
.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
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