clojure意想不到的选择和合并行为。set/join
The unexpected selection and merging behavior of clojure.set/join
我想覆盖一些与某些特定值相关的默认值,但偶然发现了一些我不太理解的行为。
(clojure.set/join
#{
{:a 1 :b nil :c "2"}
{:a 2 :b "2" :c nil}
{:a 3 :b 1 :c 5}
}
#{
{:a 1 :b 44}
{:a 3 :b 11 :c 55}
}
{:a :a})
解析为 #{{:a 3, :c 5, :b 1} {:c "2", :a 1, :b nil}}
。
如果我翻转参数,我会得到相同的结果。
(clojure.set/join
#{
{:a 1 :b 44}
{:a 3 :b 11 :c 55}
}
#{
{:a 1 :b nil :c "2"}
{:a 2 :b "2" :c nil}
{:a 3 :b 1 :c 5}
}
{:a :a})
也解析为 #{{:a 3, :c 5, :b 1} {:c "2", :a 1, :b nil}}
。
我的预期(至少从第一个声明开始)是 #{{:a 3, :c 55, :b 11} {:c "2", :a 1, :b 44}}
。
第一个问题:为什么我没有得到我期望的结果?
第二个问题:为什么无论参数顺序如何,我都会得到相同的结果?
以防万一有人想要我解决开头所述问题的方法
(
(fn [default, data, key]
(->> default
(map (fn [defaultEntry]
(merge
defaultEntry
(->> data (filter (fn [dataEntry] (= (key defaultEntry) (key dataEntry)))) first)
)
))
)
)
#{
{:a 1 :b nil :c "2"}
{:a 2 :b "2" :c nil}
{:a 3 :b 1 :c 5}
}
#{
{:a 1 :b 44}
{:a 3 :b 11 :c 55}
}
:a
)
(警告:假设 key
是唯一的!)
从clojure.set/join code我们可以看出,元素较少的集合用作索引,元素较多的集合通过查找索引中的每个元素并为每个找到的元素保留合并版本来减少项目。
(defn join
"When passed 2 rels, returns the rel corresponding to the natural
join. When passed an additional keymap, joins on the corresponding
keys."
; 2-arity version removed for brevity
([xrel yrel km] ;arbitrary key mapping
(let [[r s k] (if (<= (count xrel) (count yrel))
[xrel yrel (map-invert km)]
[yrel xrel km])
idx (index r (vals k))]
(reduce (fn [ret x]
(let [found (idx (rename-keys (select-keys x (keys k)) k))]
(if found
(reduce #(conj %1 (merge %2 x)) ret found)
ret)))
#{} s))))
这就是为什么您使用 (set/join a b)
或 (set/join b a)
会得到相同的结果,除非这两个集合的长度相同,这不是您的情况。
这也解释了为什么你得到 #{{:a 3, :c 5, :b 1} {:c "2", :a 1, :b nil}}
而不是 #{{:a 3, :c 55, :b 11} {:c "2", :a 1, :b 44}}
:当匹配时,来自较长集合的映射中的值优先于来自较短集合的值。
我想覆盖一些与某些特定值相关的默认值,但偶然发现了一些我不太理解的行为。
(clojure.set/join
#{
{:a 1 :b nil :c "2"}
{:a 2 :b "2" :c nil}
{:a 3 :b 1 :c 5}
}
#{
{:a 1 :b 44}
{:a 3 :b 11 :c 55}
}
{:a :a})
解析为 #{{:a 3, :c 5, :b 1} {:c "2", :a 1, :b nil}}
。
如果我翻转参数,我会得到相同的结果。
(clojure.set/join
#{
{:a 1 :b 44}
{:a 3 :b 11 :c 55}
}
#{
{:a 1 :b nil :c "2"}
{:a 2 :b "2" :c nil}
{:a 3 :b 1 :c 5}
}
{:a :a})
也解析为 #{{:a 3, :c 5, :b 1} {:c "2", :a 1, :b nil}}
。
我的预期(至少从第一个声明开始)是 #{{:a 3, :c 55, :b 11} {:c "2", :a 1, :b 44}}
。
第一个问题:为什么我没有得到我期望的结果?
第二个问题:为什么无论参数顺序如何,我都会得到相同的结果?
以防万一有人想要我解决开头所述问题的方法
(
(fn [default, data, key]
(->> default
(map (fn [defaultEntry]
(merge
defaultEntry
(->> data (filter (fn [dataEntry] (= (key defaultEntry) (key dataEntry)))) first)
)
))
)
)
#{
{:a 1 :b nil :c "2"}
{:a 2 :b "2" :c nil}
{:a 3 :b 1 :c 5}
}
#{
{:a 1 :b 44}
{:a 3 :b 11 :c 55}
}
:a
)
(警告:假设 key
是唯一的!)
从clojure.set/join code我们可以看出,元素较少的集合用作索引,元素较多的集合通过查找索引中的每个元素并为每个找到的元素保留合并版本来减少项目。
(defn join
"When passed 2 rels, returns the rel corresponding to the natural
join. When passed an additional keymap, joins on the corresponding
keys."
; 2-arity version removed for brevity
([xrel yrel km] ;arbitrary key mapping
(let [[r s k] (if (<= (count xrel) (count yrel))
[xrel yrel (map-invert km)]
[yrel xrel km])
idx (index r (vals k))]
(reduce (fn [ret x]
(let [found (idx (rename-keys (select-keys x (keys k)) k))]
(if found
(reduce #(conj %1 (merge %2 x)) ret found)
ret)))
#{} s))))
这就是为什么您使用 (set/join a b)
或 (set/join b a)
会得到相同的结果,除非这两个集合的长度相同,这不是您的情况。
这也解释了为什么你得到 #{{:a 3, :c 5, :b 1} {:c "2", :a 1, :b nil}}
而不是 #{{:a 3, :c 55, :b 11} {:c "2", :a 1, :b 44}}
:当匹配时,来自较长集合的映射中的值优先于来自较短集合的值。