在 Clojure 的嵌套映射中关联多个键/值的惯用方法是啥?

Posted

技术标签:

【中文标题】在 Clojure 的嵌套映射中关联多个键/值的惯用方法是啥?【英文标题】:What is the idiomatic way to assoc several keys/values in a nested map in Clojure?在 Clojure 的嵌套映射中关联多个键/值的惯用方法是什么? 【发布时间】:2011-05-28 13:51:07 【问题描述】:

想象一下你有一张这样的地图:

(def person 
  :name 
    :first-name "John"
    :middle-name "Michael"
    :last-name "Smith" )

在一个表达式中更改与 :first-name 和 :last-name 相关的值的惯用方法是什么?

(澄清:假设您要将 :first-name 设置为“Bob”,将 :last-name 设置为“Doe”。假设此映射中还有一些我们想要保留的其他值,因此构造从头开始不是一种选择)

【问题讨论】:

【参考方案1】:

这里有几种方法。

user> (update-in person [:name] assoc :first-name "Bob" :last-name "Doe")
:name :middle-name "Michael", :last-name "Doe", :first-name "Bob"

user> (update-in person [:name] merge :first-name "Bob" :last-name "Doe")
:name :middle-name "Michael", :last-name "Doe", :first-name "Bob"

user> (update-in person [:name] into :first-name "Bob" :last-name "Doe")
:name :middle-name "Michael", :last-name "Doe", :first-name "Bob"

user> (-> person 
          (assoc-in [:name :first-name] "Bob")
          (assoc-in [:name :last-name]  "Doe"))
:name :middle-name "Michael", :last-name "Doe", :first-name "Bob"

编辑

update-in 在你的地图上递归 assocs。在这种情况下,它大致相当于:

user> (assoc person :name 
             (assoc (:name person) 
                    :first-name "Bob" 
                    :last-name "Doe"))

随着您深入一系列嵌套映射,键的重复变得越来越乏味。 update-in 的递归可让您避免一遍又一遍地重复键(例如 :name);中间结果存储在递归调用之间的堆栈中。查看update-in 的源代码,了解它是如何完成的。

user> (def foo :bar :baz :quux 123)
#'user/foo

user> (assoc foo :bar 
             (assoc (:bar foo) :baz 
                    (assoc (:baz (:bar foo)) :quux 
                           (inc (:quux (:baz (:bar foo)))))))
:bar :baz :quux 124

user> (update-in foo [:bar :baz :quux] inc)
:bar :baz :quux 124

assoc 是动态的(update-inassoc-in 和大多数其他对 Clojure 数据结构进行操作的 Clojure 函数也是如此)。如果assoc 到地图上,则返回地图。如果你assoc 到一个向量上,它会返回一个向量。查看 assoc 的源代码并查看 Clojure 源代码中的 RT.java 以了解详细信息。

【讨论】:

谢谢!第一条语句的语法是如何工作的? assoc 怎么知道它在通过“update-in”传递给它的地图上运行?看起来很整洁,但是编译器怎么不糊涂呢? assoc 不在乎是否只获取地图和一些 args(成对)然后它会做它的事情。您返回的地图将通过更新语义放入正确的位置,然后作为孔地图返回。 不确定您在问什么,但我添加了一些编辑以扩展答案,希望对您有所帮助。 再次感谢。我在 'assoc :first-name "Bob" :last-name "Doe"' 部分苦苦挣扎,试图弄清楚 assoc 如何“知道”它应该将键/值放入哪个映射(鉴于通常的语法for assoc 在键/值开始之前包含 map 变量,例如 (assoc myMap :first-name "Bob" :last-name "Doe")。我会再玩这个。 我这样做了:(defn multi-assoc-in [m & kvvp] (reduce #(apply assoc-in %1 %2) m (partition 2 kvvp))) 其中 kvvp 是 kevvector-value 对,与您传递给 assoc-in 的相同。现在我可以做(swap! myatom multi-assoc-in [:a :b] "whatever" [:c :d :e] "blabla")【参考方案2】:

我不确定我的情况是否完全一样,但我有要应用的更改列表:

(def updates [[[:name :first-name] "Bob"] 
              [[:name :last-name] "Doe"]])

在这种情况下,您可以像这样使用 assoc-in 减少该列表:

(reduce #(assoc-in %1 (first %2) (second %2)) person updates)

【讨论】:

以上是关于在 Clojure 的嵌套映射中关联多个键/值的惯用方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Clojure / Clojurescript:按多个值的地图分组

Clojure映射中的默认值

在 Clojure 中将元组数组转换为哈希映射

如何在 Clojure 中递归展平任意嵌套的向量和映射?

无法使用 clojure 模式库在映射中使用字符串键验证值

在没有嵌套 let 的情况下在 Clojure 中绑定多个相关变量