0
(def data {"Bob"    {"A" 3.5  "B" 4.5 "C" 2.0}
           "Jane"   {"A" 2.0  "B" 1.5 "D" 4.0}})

打电话

(merge-with + (data "Bob") (data "Jane"))

生产

 {"A" 5.5, "B" 6.0, "C" 2.0 "D" 4.0}

我只想创建一个合并的地图,但只适用于公共键。我正在寻找的结果是

   {"A" 5.5, "B" 6.0}

在clojure中这样做的好方法是什么?

4

6 回答 6

5

这是一个相当简单的单通道方法,它应该优于迄今为止建议的多通道方法,并且不会特别难以阅读:

(defn merge-matching [f a b]
  (into {}
        (for [[k v] a
              :let [e (find b k)]
              :when e]
          [k (f v (val e))])))
于 2013-09-15T10:52:54.303 回答
4

使用瞬态的面向性能的解决方案,reduce-kv以及迭代较小映射的大小检查:

(defn merge-common-with [f m1 m2]
  (let [[a b] (if (< (count m1) (count m2))
                [m1 m2]
                [m2 m1])]
    (persistent!
     (reduce-kv (fn [out k v]
                  (if (contains? b k)
                    (assoc! out k (f (get a k) (get b k)))
                    out))
                (transient {})
                a))))

在 REPL 中,使用问题文本中的示例数据:

(merge-common-with + (data "Bob") (data "Jane"))
;= {"A" 5.5, "B" 6.0}

请注意,虽然我希望上述方法在许多情况下是最快的方法,但我肯定会使用您的实际用例的典型数据进行基准测试。这是一个基于标准的基准测试,使用data来自问题文本(merge-common-with在这里获胜):

(require '[criterium.core :as c])

(def a (data "Bob"))
(def b (data "Jane"))

;; Hendekagon's elegant approach amended to select-keys on both sides
(defn merge-common-with* [f a b]
  (merge-with f
              (select-keys a (keys b))
              (select-keys b (keys a))))

;; benchmarks for three approaches follow, fastest to slowest

(c/bench (merge-common-with + a b))
Evaluation count : 74876640 in 60 samples of 1247944 calls.
             Execution time mean : 783.233604 ns
    Execution time std-deviation : 7.660391 ns
   Execution time lower quantile : 771.514052 ns ( 2.5%)
   Execution time upper quantile : 802.622953 ns (97.5%)
                   Overhead used : 1.266543 ns

Found 3 outliers in 60 samples (5.0000 %)
    low-severe   3 (5.0000 %)
 Variance from outliers : 1.6389 % Variance is slightly inflated by outliers

(c/bench (merge-matching + a b)) ; amalloy's approach
Evaluation count : 57320640 in 60 samples of 955344 calls.
             Execution time mean : 1.047921 µs
    Execution time std-deviation : 16.221173 ns
   Execution time lower quantile : 1.025001 µs ( 2.5%)
   Execution time upper quantile : 1.076081 µs (97.5%)
                   Overhead used : 1.266543 ns

(c/bench (merge-common-with* + a b))
WARNING: Final GC required 3.4556868188006065 % of runtime
Evaluation count : 33121200 in 60 samples of 552020 calls.
             Execution time mean : 1.862483 µs
    Execution time std-deviation : 26.008801 ns
   Execution time lower quantile : 1.821841 µs ( 2.5%)
   Execution time upper quantile : 1.914336 µs (97.5%)
                   Overhead used : 1.266543 ns

Found 1 outliers in 60 samples (1.6667 %)
    low-severe   1 (1.6667 %)
 Variance from outliers : 1.6389 % Variance is slightly inflated by outliers
于 2013-09-15T11:28:32.680 回答
1
(merge-with merge-fn A (select-keys B (keys A)))
于 2013-09-15T02:10:55.180 回答
0
user> (let [data {"Bob"    {"A" 3.5  "B" 4.5 "C" 2.0}
                  "Jane"   {"A" 2.0  "B" 1.5 "D" 4.0}}
            common-keys (apply clojure.set/intersection
                               (map (comp set keys second) data))]
        (apply merge-with + (map #(select-keys % common-keys) (vals data))))
{"B" 6.0, "A" 5.5}

我对其进行了概括,因此它可以对传入的数据更加不可知

于 2013-09-15T02:14:38.370 回答
0

如果你总是只想合并两个对象,你也可以这样做。

(into {} 
  (for [[kx vx] (data "Bob") 
        [ky vy] (data "Jane") 
        :when (= kx ky)] 
    {kx (+ vx vy)})))

如果要合并多个对象,可以将上面的代码定义为函数并像这样使用 reduce。

(defn merge-objects [obj1 obj2] 
  (into {} (for [[kx vx] obj1 [ky vy] obj2  :when (= kx ky)] {kx (+ vx vy)})))

(reduce merge-objects (map data ["Bob" "Jane"]))

我不确定这可能会对性能产生任何影响,因为您实际上是在两个地图上进行迭代。但如果您的地图很小,您可能不必担心。

于 2013-09-15T06:58:36.663 回答
0
(defn reduce-merge [& maps]
  (when (some identity maps)
    (reduce #(select-keys (or %2 %1) (keys %1)) maps)))

对我来说效果很好,or就是在地图列表中吞下零。不处理深度合并或提供碰撞功能(总是会发生)。

于 2015-03-20T16:12:07.280 回答