Clojure merge-with
clojure
defn
(defn merge-with
"Returns a map that consists of the rest of the maps conj-ed onto
the first. If a key occurs in more than one map, the mapping(s)
from the latter (left-to-right) will be combined with the mapping in
the result by calling (f val-in-result val-in-latter)."
{:added "1.0"
:static true}
[f & maps]
(when (some identity maps)
(let [merge-entry (fn [m e]
(let [k (key e) v (val e)]
(if (contains? m k)
(assoc m k (f (get m k) v))
(assoc m k v))))
merge2 (fn [m1 m2]
(reduce1 merge-entry (or m1 {}) (seq m2)))]
(reduce1 merge2 maps))))
Examples
(merge-with +
{:a 1 :b 2}
{:a 9 :b 98 :c 0}
{:a 9 :d 100})
=> {:a 19, :b 100, :c 0, :d 100}
세 개의 map에 대해 같은 key값을 가진 엔트리에 +
함수를 적용한 결과가 나왔다. map 의 개수는 몇 개를 줘도 상관없다.
만약 여러 개의 map이 리스트에 들어있는 상태라면 apply
를 쓴다.
(apply merge-with +
[{:a 1 :b 2},
{:a 9 :b 98 :c 0},
{:a 9 :d 100}])
=> {:a 19, :b 100, :c 0, :d 100}
이번엔 +
함수가 아니라 list
함수를 써보자.
(merge-with list
{:a 1 :b 2},
{:a 9 :b 98 :c 0},
{:a 7 :d 100})
=> {:a ((1 9) 7), :b (2 98), :c 0, :d 100}
(1 9 7)
처럼 나오길 바랐지만 ((1 9) 7)
이 나왔다.
하지만 merge-with
가 어떻게 동작하는지 알 수 있다.
아마 다음과 같이 작동했을 것이다.
(list (list 1 9) 7)
=> ((1 9) 7)
그러므로 map의 각 엔트리에 들어있는 value값이 리스트로 되어 있다면 into
를 사용할 수 있다.
(merge-with into
{:a [1 2] :b [4 7] },
{:a [1 4] :b [98] :c [0] },
{:a [2] :d [100]})
=> {:a [1 2 1 4 2], :b [4 7 98], :c [0], :d [100]}