Clojure中更新map多个值的惯用方式

5

这可能很简单,但我就是无法克服它。 我有一个嵌套地图的数据结构,就像这样:

(def m {:1 {:1 2 :2 5 :3 10} :2 {:1 2 :2 50 :3 25} :3 {:1 42 :2 23 :3 4}})

我需要设置每个 m[i][i]=0。这在非函数式语言中很简单,但我无法在Clojure上使其工作。考虑到我确实有一个包含每个可能值的向量(称之为v),应该如何惯用地完成此操作?
使用 (map #(def m (assoc-in m [% %] 0)) v) 将起作用,但在 map 函数内使用 def 并不正确。 将 m 改造成原子版本并使用 swap! 似乎更好。但它也很慢。
(def am (atom m))
(map #(swap! am assoc-in[% %] 0) v)

什么是最好/正确的方法来实现这个?

更新

这里有一些很好的答案。我在这里发布了一个后续问题Clojure: iterate over map of sets,它与这个问题有一定的关联。


对于更新深度嵌套结构的更复杂情况,可能会对Specter感兴趣:https://github.com/nathanmarz/specter | https://www.youtube.com/watch?v=mXZxkpX5nt8 - Paul
3个回答

9
你说得没错,在函数内使用 def 是不好的做法。在 map 内部使用有副作用的函数(例如 swap)也是不好的做法。此外,map 是惰性的,因此你的 map/swap 尝试除非被强制执行(例如使用 dorun),否则实际上不会产生任何效果。
既然我们已经讲了什么不应该做,现在让我们看看怎么做吧 ;-)
有几种方法可以采取。对于从命令式编程范式转换过来的人来说,可能最容易的方法是使用 loop:
(defn update-m [m v]
  (loop [v' v
         m' m]
    (if (empty? v')
      ;; then (we're done => return result)
      m'
      ;; else (more to go => process next element)
      (let [i (first v')]
        (recur (rest v')                  ;; iterate over v
               (assoc-in m' [i i] 0)))))) ;; accumulate result in m'

然而,在函数式编程范式中,loop是一个相对较低级的构造。我们可以注意到一个模式:我们正在循环遍历v元素,并在m'中累积更改。这个模式可以通过reduce函数来捕捉:
(defn update-m [m v]
  (reduce (fn [m' i]
            (assoc-in m' [i i] 0)) ;; accumulate changes
          m   ;; initial-value
          v)) ;; collection to loop over

这个表单要短得多,因为它不需要像“loop”形式那样的样板代码。 “reduce”形式可能一开始阅读起来不太容易,但一旦您习惯了函数式代码,它就会变得更自然。
现在我们有了“update-m”,我们可以在整个程序中使用它来转换映射。例如,我们可以使用它来“swap!”原子。按照上面的示例:
(swap! am update-m v)

3
女士们先生们,这就是如何回答问题。 - Arthur Camara

4

在函数内部使用def肯定不是推荐的做法。面向对象的方法是查看数据结构并修改值。而函数式编程的方法是构建一个新的数据结构,表示旧数据结构已更改的值。因此,我们可以像这样做:

(into {} (map (fn [[k v]] [k (assoc v k 0)]) m))

这里我们正在对m进行映射,方法与您在第一个示例中所做的方式非常相似。但是,我们返回一个包含键值对的[]元组,而不是修改m。我们可以将这个元组列表放回到一个map中。

2
其他答案都不错,但为了完整起见,这里提供一个稍微更短的版本,使用一个 for 推导式。我觉得它更易读,但这是个人口味问题:
(into {} (for [[k v] m] {k (assoc v k 0)}))

但是这不是忽略了OP的请求提供要设置为0的键列表吗? - cfrick
@cfrick 我不确定这个向量是否必要。OP的字面要求是“将每个m[i][i]设置为0”。 - Diego Basch
如果 v 总是等于 (keys m),那么它肯定不是这样的。 - cfrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接