两张地图之间的差异

时间:2010-08-02 11:13:15

标签: java algorithm clojure hashmap

我需要非常有效地比较Clojure / Java中的两个映射,并返回由Java的.equals(..)确定的差异,其中nil / null等效于“not present”。

即。我正在寻找一种最有效的方式来编写像:

这样的函数
(map-difference
  {:a 1, :b nil, :c 2, :d 3}
  {:a 1, :b "Hidden", :c 3, :e 5})

=> {:b nil, :c 2, :d 3, :e nil}

我更喜欢将不可变的Clojure映射作为输出,但如果性能提升很重要,那么Java映射也会很好。

对于它的价值,我的基本测试用例/对行为的期望是对于任何两个映射a和b,以下将是相等的(最多等于null =“Not present”):

a 
(merge b (difference a b))

实现这一目标的最佳方式是什么?

7 个答案:

答案 0 :(得分:11)

我不确定最有效的方法是什么,但这里有一些可能有用的东西:

  1. 问题文本中对行为的基本期望是不可能的:如果ab是地图,b包含至少一个{{1}中不存在的密钥},a不能等于(merge b <sth>)

  2. 如果您最终使用互操作解决方案,但需要在某个时刻返回a,那么总是

    PersistentHashMap
  3. 如果需要将Clojure映射的键集传递给Java方法,可以使用

    (clojure.lang.PersistentHashMap/create
     (doto (java.util.HashMap.)
       (.put :foo 1)
       (.put :bar 2)))
    ; => {:foo 1 :bar 2}
    
  4. 如果所涉及的所有密钥都保证为(.keySet {:foo 1 :bar 2}) ; => #< [:foo, :bar]> ,则可利用此密钥在具有许多密钥的映射(排序和合并扫描)上有效计算Comparable。对于无约束键,这当然是禁止的,对于小地图来说,它实际上可能会损害性能。

  5. 如果只是为了设置基线性能预期,最好有一个用Clojure编写的版本。这是一个:(更新)

    difference

    我认为只做(defn map-difference [m1 m2] (loop [m (transient {}) ks (concat (keys m1) (keys m2))] (if-let [k (first ks)] (let [e1 (find m1 k) e2 (find m2 k)] (cond (and e1 e2 (not= (e1 1) (e2 1))) (recur (assoc! m k (e1 1)) (next ks)) (not e1) (recur (assoc! m k (e2 1)) (next ks)) (not e2) (recur (assoc! m k (e1 1)) (next ks)) :else (recur m (next ks)))) (persistent! m)))) 并且可能复制某些工作在大多数情况下可能比在每一步检查给定密钥都在“其他地图”中更有效。

  6. 总结答案,这是一个非常简单的基于集合的版本,具有良好的属性,它说明了它的作用 - 如果我误解了规范,它应该在这里很明显。 : - )

    (concat (keys m1) (keys m2))

答案 1 :(得分:6)

在Java中,Google Commons Collections提供了一个非常performant solution

答案 2 :(得分:3)

  1. Clojure地图很好,因为阅读clojure地图非常快。

  2. 我不能回答你,但我可以给你一些看法。 Brenton Ashworth做了一个testtool,用地图比较解决了问题。也许您可以查看他的代码以获得实施提示。 http://formpluslogic.blogspot.com/2010/07/better-clojure-test-results-with-deview.htmlhttp://github.com/brentonashworth/deview

  3. 我认为没有更好的实施方法来比较密钥,并查看它们是否不同。

答案 3 :(得分:3)

使用内置集合API:

Set<Map.Entry<K,V>> difference = a.entrySet().removeAll(b.entrySet());

如果您需要将其转换回地图,则必须进行迭代。在这种情况下,我建议:

Map<K,V> result = new HashMap<K,V>(Math.max(a.size()), b.size()));
Set<Map.Entry<K,V>> filter = b.entrySet();
for( Map.Entry<K,V> entry : a.entrySet ) {
    if( !filter.contains( entry ) {
        result.put(entry.getKey(), entry.getValue());
    }
}

答案 4 :(得分:3)

我不确定它的表现

(defn map-difference
  [orig other]
  (let [changed (set/difference (set orig) (set other))
        added (set/difference (set (keys other)) (set (keys orig)))]
    (reduce (fn [acc key]
              (assoc acc key :missing))
      (into {} changed)
      added)))

我使用:missing键来避免原始地图中nil值与缺失密钥之间存在歧义 - 您当然可以将其更改为nil

答案 5 :(得分:2)

您也可以使用Google的Guava图书馆中的Maps.difference(..)方法

答案 6 :(得分:0)

怎么样......

(defn map-diff [m1 m2]
  ;; m1: hashmap
  ;; m2: hashmap
  ;; => the difference between them
  (reduce merge
          (map #(hash-map % (- (or (% m1) 0) (or (% m2) 0)))
               (keys (merge m1 m2)))))