Clojure: iterate over map of sets

為{幸葍}努か 提交于 2019-12-23 17:21:35

问题


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?


回答1:


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}}



回答2:


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)



回答3:


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.



来源:https://stackoverflow.com/questions/32688146/clojure-iterate-over-map-of-sets

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!