这可能很简单,但我无法克服它。 我有一个嵌套映射的数据结构,如下所示:
(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)
会有效,但在def
上的函数中使用map
似乎并不合适。
使m成为原子版并使用swap!
似乎更好。但不是很多它似乎也很慢。
(def am (atom m))
(map #(swap! am assoc-in[% %] 0) v)
这样做的最佳/正确方法是什么?
更新
这里有一些很棒的答案。我在这里发布了一个跟进问题Clojure: iterate over map of sets,与此问题密切相关,但没有那么多。
答案 0 :(得分: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'
然而,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)
答案 1 :(得分:2)
当然不建议在函数内部使用def
。查看问题的OO方法是查看数据结构并修改值。查看问题的功能方法是构建一个新的数据结构,该结构表示值已更改的旧数据结构。所以我们可以做这样的事情
(into {} (map (fn [[k v]] [k (assoc v k 0)]) m))
我们在这里做的是映射m
,就像你在第一个例子中所做的那样。但是,我们不是修改m
而是返回[]
元组的键和值。这个元组列表然后我们可以放回到地图中。
答案 2 :(得分:1)
其他答案都很好,但为了完整起见,这是一个使用for
理解的略短版本。我发现它更具可读性,但这是一个品味问题:
(into {} (for [[k v] m] {k (assoc v k 0)}))