【发布时间】:2022-01-19 19:28:28
【问题描述】:
您好,我有 2 张这样的地图(可能有多个地图)
(def map1 {:a {:b 1} :c ["dog"]})
(def map2 {:a {:b 2} :c ["cat"]})
我需要将其作为返回 {:a {:b 3} :c ["dog" "cat"]}
我该怎么做?
【问题讨论】:
标签: clojure
您好,我有 2 张这样的地图(可能有多个地图)
(def map1 {:a {:b 1} :c ["dog"]})
(def map2 {:a {:b 2} :c ["cat"]})
我需要将其作为返回 {:a {:b 3} :c ["dog" "cat"]}
我该怎么做?
【问题讨论】:
标签: clojure
这是一个想法。我没有对此进行测试,而且我很确定会出现更聪明的解决方案……尤其是现在我说这是一个答案(互联网上有人说某事错误的效果): )
(reduce
(fn [accum item]
(let [b-accum (get-in accum [:a :b])
b-item (get-in item [:a :b])
new-b (+ b-accum b-item)
new-c (vec (concat (:c accum) (:c item))]
{:a {:b new-b} :c new-c}))
[map1 map2])
【讨论】:
您可以使用merge-with 和自定义函数来解决合并地图时的冲突。
(defn my-merge [a b]
(merge-with (fn [a b]
(if (map? a)
(merge-with + a b)
(into a b)))
a b))
或者递归定义:
(defn my-merge [a b]
(cond (vector? a) (into a b)
(number? a) (+ a b)
(map? a) (merge-with my-merge a b)
:else (throw (ex-info "Unsupported values" {:values [a b]}))))
【讨论】:
您可以使用泛型函数——我们在这里称它为o+(运算符+)——通过利用Clojure 的多重分派能力。通过多次调度,您可以避免 if - else 或 cond 子句 - 用于区分参数类型/类 - 从而代码保持可扩展性(可以添加更多案例而无需更改现有代码)。 defmulti 定义末尾的函数是调度函数。在 defmethod 表单中 - 在实际函数参数列表(向量)之前,您列出了 Clojure 在决定使用哪种方法时查找的调度案例。
(defmulti o+ (fn [& args] (mapv class args)))
(defmethod o+ [Number Number] [x y] (+ x y)) ;; Numbers get added
(defmethod o+ [clojure.lang.PersistentVector clojure.lang.PersistentVector]
[x y] (into (empty x) (concat x y))) ;; Vectors concatentated
(defmethod o+ [clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap]
[x y] (merge-with o+ x y)) ;; Maps merged-with #'o+ (recursive definition!)
您可以将两个映射与(现在递归定义的)o+ 运算符融合:
(def map1 {:a {:b 1} :c ["dog"]})
(def map2 {:a {:b 2} :c ["cat"]})
(o+ map1 map2) ;; or originally: (merge-with o+ map1 map2)
;; => {:a {:b 3}, :c ["dog" "cat"]}
由于此定义的递归特性 - 只要它们具有相同的结构,这也适用于更深层嵌套的映射 - 并且仅使用已定义的类案例(否则可以添加更多案例):
(def mapA {:a {:b 2 :c {:d 1 :e ["a"] :f 3}} :g ["b"]})
(def mapB {:a {:b 3 :c {:d 4 :e ["b" "e"] :f 5}} :g ["c"]})
(o+ mapA mapB)
;;=> {:a {:b 5, :c {:d 5, :e ["a" "b" "e"], :f 8}}, :g ["b" "c"]}
reduce 一次添加更多地图而且只要你可以在两个对象上应用o+,你就可以使用reduce处理任意数量的地图:
(def mapA {:a {:b 2 :c {:d 1 :e ["a"] :f 3}} :g ["b"]})
(def mapB {:a {:b 3 :c {:d 4 :e ["b" "e"] :f 5}} :g ["c"]})
(def mapC {:a {:b 1 :c {:d 3 :e ["c"] :f 1}} :g ["a"]})
(reduce o+ [mapA mapB mapC]) ;; this vector could contain much more maps!
;; => {:a {:b 6, :c {:d 8, :e ["a" "b" "e" "c"], :f 9}}, :g ["b" "c" "a"]}
;; or we could define in addition:
(defmethod o+ :default [& args] (reduce o+ args))
;; which makes `o+` to a variadic function (function which can be
;; called with as many arguments you want)
;; then, whenever we add more than two arguments, it will be activated:
(o+ mapA mapB mapC)
;; => {:a {:b 6, :c {:d 8, :e ["a" "b" "e" "c"], :f 9}}, :g ["b" "c" "a"]}
;; and this also works:
(o+ 1 2 3 4 5 6)
;; => 21
(o+ ["a"] ["b" "c"] ["d"])
;; => ["a" "b" "c" "d"]
【讨论】:
(require '[net.cgrand.xforms :as x])
(let [map1 {:a {:b 1} :c ["dog"]}
map2 {:a {:b 2} :c ["cat"]}]
(->> [map1 map2]
(into {}
(x/multiplex
{:a (comp (map :a) (map :b) (x/reduce +) (x/into [:b]) (x/into {}))
:c (comp (mapcat :c) (x/reduce conj))}))))
【讨论】: