Clojure惯用法更新地图的多个值

Art*_*ara 2 reduce dictionary clojure

这可能很简单,但我无法克服它.我有一个嵌套映射的数据结构,如下所示:

(def m {:1 {:1 2 :2 5 :3 10} :2 {:1 2 :2 50 :3 25} :3 {:1 42 :2 23 :3 4}})
Run Code Online (Sandbox Code Playgroud)

我需要设置每一个m[i][i]=0.这在非函数式语言中很简单,但我不能在Clojure上使用它.考虑到我确实有一个具有各种可能值的向量,这样做的惯用方法是怎样的?(让我们称之为v)

(map #(def m (assoc-in m [% %] 0)) v)会工作,但def在函数内使用map似乎不对.使m成为原子版并使用swap!似乎更好.但并不多,它似乎也很慢.

(def am (atom m))
(map #(swap! am assoc-in[% %] 0) v)
Run Code Online (Sandbox Code Playgroud)

这样做的最佳/正确方法是什么?

UPDATE

这里有一些很棒的答案.我在这里发布了一个后续问题Clojure:迭代与这些关系紧密相关的集合的映射,但没有那么多.

Nat*_*vis 6

你是对的,def在函数内部使用它是不好的形式.在内部使用具有副作用(例如swap)的函数也是不好的形式map.更进一步,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'
Run Code Online (Sandbox Code Playgroud)

然而,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
Run Code Online (Sandbox Code Playgroud)

这种形式相当短,因为它不需要loop表格所需的样板代码.该reduce形态可能不那么容易在第一次阅读,但一旦你习惯的功能代码,它会变得更加自然.

现在update-m我们可以使用它来转换程序中的地图.例如,我们可以将它用于swap!原子.按照上面的例子:

(swap! am update-m v)
Run Code Online (Sandbox Code Playgroud)

  • 女士们,先生们,这就是你回答问题的方法. (3认同)