【问题标题】:Difference between two maps两张地图的区别
【发布时间】:2011-03-24 03:54:58
【问题描述】:

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

即我正在寻找编写如下函数的最有效方法:

(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))

实现这一点的最佳方法是什么?

【问题讨论】:

  • 老故事,但我想知道 Clojure 1.3 中的 clojure.data.diff 将如何解决您的问题?

标签: java algorithm clojure hashmap


【解决方案1】:

我不确定最有效的方法是什么,但这里有几件事可能有用:

    1234563等于a。
  1. 如果您最终使用互操作解决方案,但在某些时候需要返回到 PersistentHashMap,那么总会有

    (clojure.lang.PersistentHashMap/create
     (doto (java.util.HashMap.)
       (.put :foo 1)
       (.put :bar 2)))
    ; => {:foo 1 :bar 2}
    
  2. 如果需要将 Clojure 映射的键集传递给 Java 方法,可以使用

    (.keySet {:foo 1 :bar 2})
    ; => #< [:foo, :bar]>
    
  3. 如果保证所有涉及的键都是Comparable,则可以利用它在具有许多键的映射上有效计算difference(排序和合并扫描)。对于不受约束的键,这当然是不行的,对于小地图,它实际上可能会损害性能。

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

    (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))))
    

    我认为,在大多数情况下,只做(concat (keys m1) (keys m2)) 并可能重复一些工作可能比在每一步都检查“另一个地图”中的给定键更有效。

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

(defn map-difference [m1 m2]
  (let [ks1 (set (keys m1))
        ks2 (set (keys m2))
        ks1-ks2 (set/difference ks1 ks2)
        ks2-ks1 (set/difference ks2 ks1)
        ks1*ks2 (set/intersection ks1 ks2)]
    (merge (select-keys m1 ks1-ks2)
           (select-keys m2 ks2-ks1)
           (select-keys m1
                        (remove (fn [k] (= (m1 k) (m2 k)))
                                ks1*ks2)))))

【讨论】:

  • 出色的回答米哈尔,非常感谢!关于 1)只有一点,如果您指定 nil 等同于不存在(根据问题),我认为可以通过将 map-difference 分配 nil 给需要“删除”的键来实现该要求。
  • 我希望你知道defrecords?如果您不需要通用地图,也许这是一个解决方案。
  • @mikera:谢谢,很高兴听到这个消息。 :-) 至于 1),这是一个很好的观点,它应该是对任何解决方案的一个小调整——感谢您的更正! @nickik:我不确定你的想法。你能详细说明一下吗?
  • @Michal Marczyk - 你优雅的第一个基线解决方案发生了什么?我想看看它,现在它不见了。
  • @Adam Schmideg:我发现它与规范不符并将其删除。您可以在此答案的修订历史记录中看到它(链接到紧接答案文本下方的“编辑过的某某时间”通知的“某某时间前”部分)。
【解决方案2】:

在 Java 中,Google Commons Collections 提供了一个相当 performant solution。

【讨论】:

  • 谢谢!这很有用,尽管比我所寻找的更通用(它会生成一整套地图比较,而不是我正在寻找的简单差异图)
【解决方案3】:
  1. Clojure 映射会很好,因为读取 clojure 映射非常快。

  2. 我不能回答你,但我可以给你一些东西看看。 Brenton Ashworth 制作了一个测试工具,通过地图比较解决了这个问题。也许您可以查看他的代码以获取实施提示。 http://formpluslogic.blogspot.com/2010/07/better-clojure-test-results-with-deview.html 和 http://github.com/brentonashworth/deview

  3. 我认为没有更好的实现来比较键并查找是否不同。

【讨论】:

  • 请修正第三点的语法;这没有意义。
【解决方案4】:

使用内置的集合 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());
    }
}

【讨论】:

    【解决方案5】:

    我不确定它的性能

    (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。

    【讨论】:

      【解决方案6】:

      您也可以只使用 Google 的 Guava 库中的 Maps.difference(..) 方法

      【讨论】:

        【解决方案7】:

        怎么样……

        (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)))))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-12-14
          • 2014-09-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多