我有两张地图:
(def people {:1 "John" :2 "Paul" :3 "Ringo" :4 "George"})
(def band
{:data
{:members
{:1 {:id 1 :name "John"}
:2 {:id 2 :name "Paul"}}}})
我想循环遍历people
并将[:data :members]
中不存在的任何成员添加到band
,从而导致:
(def band
{:data
{:members
{:1 {:id 1 :name "John"}
:2 {:id 2 :name "Paul"}
:3 {:id 3 :name "Ringo"}
:4 {:id 4 :name "George"}}}})
这是我尝试过的:
(for [[id name] people]
(when-not
(contains? (get-in band [:data :members]) id)
(assoc-in band [:data :members id] {:id id :name name})))
哪个收益率:
({:data
{:members
{:4 {:id :4, :name "George"},
:1 {:name "John", :id 1},
:2 {:name "Paul", :id 2}}}}
nil
nil
{:data
{:members
{:1 {:name "John", :id 1},
:2 {:name "Paul", :id 2},
:3 {:id :3, :name "Ringo"}}}})
我不知道为什么我要找回band
的每个变异列表。我在这做错了什么?如何将people
中缺少的成员添加到band [:data :members]
?
答案 0 :(得分:3)
要迂腐,你不能回复任何变异的乐队。实际上,Clojure最重要的特性之一是标准类型是不可移植的,主要的集合操作返回修改后的副本而不改变原始类型。
此外,Clojure中的for
不是循环,它是列表理解。这就是为什么它总是返回每个步骤的序列。因此,不是一次一步地改变输入,而是为每个步骤的输入创建了一个新的变体,每个变量都来自不可变原始。
基于值序列制作输入的一系列更新副本的标准构造是reduce
,它将累加器的新版本和列表的每个元素传递给您的函数。
最后,你误解了:keyword
语法的作用 - 为了构造地图键,不需要为项添加前缀:几乎任何clojure值都是地图的有效键,关键字是只是一个方便的习语。
user=> (def band
{:data
{:members
{1 {:id 1 :name "John"}
2 {:id 2 :name "Paul"}}}})
#'user/band
user=> (def people {1 "John" 2 "Paul" 3 "Ringo" 4 "George"})
#'user/people
user=> (pprint
(reduce (fn [band [id name :as person]]
(if-not (contains? (get-in band [:data :members]) id)
(assoc-in band [:data :members id] {:id id :name name})
band))
band
people))
{:data
{:members
{3 {:id 3, :name "Ringo"},
4 {:id 4, :name "George"},
1 {:name "John", :id 1},
2 {:name "Paul", :id 2}}}}
nil
您可能会注意到传递给fn
的{{1}}的正文与您reduce
理解的正文基本相同。不同之处在于,我使用for
代替when-not
,而if-not
使用band
,这允许我们传播累加器(此处称为{{1}},与输入相同),而不管是否有任何新版本。