将地图值聚合到矢量中

时间:2017-06-15 03:27:57

标签: clojure

我想知道是否有人可以帮我找到与merge-with一起使用的正确功能,以便将地图值合并为单个矢量。

谢谢!

; works great -single vector
(merge-with vector {:a "b"} {:a "d"} {:a "c"})   
; {:a ["b" "d"]}

; uh-oh... now we are beginning to nest each set
(merge-with vector {:a "b"} {:a "d"} {:a "c"})
;{:a [["b" "d"] "c"]}

; what I want:
; {:a ["b" "d" "c"]}

3 个答案:

答案 0 :(得分:1)

though the approach with flatten solves your concrete problem, it is not universal. Based on your question i would guess that you need a map of keyword to vector as a result. And it works, when all the maps contain exactly same keys. But guess the following corner cases:

user>  (merge-with (comp flatten vector) {:a "b"}) 
;;=> {:a "b"} oops! you following processing probably wants {:a ["b"]}

user>  (merge-with (comp flatten vector) {:a "b"} {:c "d"})
;;=> {:a "b", :c "d"} once again!

user> (merge-with (comp flatten vector) {:a ["b"]} {:a ["c" ["d"]]})
;;=> {:a ("b" "c" "d")}
;; here i can see some inconsistent behavior, breaking the initial data form: would't you rather want {:a [["b"] ["c" ["d"]]]} ?

so, given that you are doing something for production, rather then learning, i would advice the following approach: you can make the function, merging maps, but also handling the single (or first) key appearing in the result the special way:

(defn smart-merge-with [first-val-fn merge-fn & args]
  (when (seq args)
    (reduce (fn [acc items-map]
              (reduce (fn [acc [k v]]
                        (if (contains? acc k)
                          (update acc k merge-fn v)
                          (assoc acc k (first-val-fn v))))
                      acc items-map))
            {} args))) 

now you can just wrap the first value into a vector, and then, when there is another value with the same key appears just add it to that vector:

user> (smart-merge-with vector conj {:a 10 :b 30} {:a 20 :c 30} {:c 1} {:d 100})
;;=> {:a [10 20], :b [30], :c [30 1], :d [100]}

user> (smart-merge-with vector conj {:a [10] :b 30} {:a 20 :c 30} {:c 1} {:d 100})
{:a [[10] 20], :b [30], :c [30 1], :d [100]}

in addition, now you can add more sophisticated logic to the maps' merging, like for example some accumulation:

user> (smart-merge-with (fn [x] {:items [x] :sum x})
                        (fn [x y] (-> x
                                      (update :items conj y)
                                      (update :sum + y)))
                        {:a 10 :b 20} {:b 30 :c 40} {:c 1 :d 2})
;;=> {:a {:items [10], :sum 10}, 
;;    :b {:items [20 30], :sum 50}, 
;;    :c {:items [40 1], :sum 41}, 
;;    :d {:items [2], :sum 2}}

答案 1 :(得分:0)

From this answer我们可以使用相同的原则:

(merge-with (comp  #(into [] % ) flatten vector) {:a "b"} {:a "d"} {:a "c"})
{:a ["b" "d" "c"]}

答案 2 :(得分:0)

或者滚动你自己的功能:

(merge-with #(if (vector? %1) (conj %1 %2) (vector %1 %2)) {:a "b"} {:a "d"} {:a "c"})