2010-08-02 140 views
16

我需要非常高效地比較Clojure/Java中的兩張地圖,並返回由Java的.equals(..)確定的差異,其中零/等價於「不存在」。兩張地圖之間的區別

即我在尋找最有效的方式來寫像一個函數:

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

a 
(merge b (difference a b)) 

執行此操作的最佳方法是什麼?

+1

老故事,但我不知道如何從'Clojure的1.3 clojure.data.diff'將票價上你的問題? – 2012-09-10 12:37:15

回答

11

我不知道該怎麼做絕對最有效的方式是這樣的,但這裏的一對夫婦的事情,可能是有用的:

  1. 基本對問題文本的行爲期望是不可能的:如果ab是使得b包含至少一個不存在於a中的密鑰的地圖,則(merge b <sth>)不能等於a

  2. 如果你結束了一個互操作的解決方案去,但這時需要回到一個PersistentHashMap在某些時候,總有

    (clojure.lang.PersistentHashMap/create 
    (doto (java.util.HashMap.) 
        (.put :foo 1) 
        (.put :bar 2))) 
    ; => {:foo 1 :bar 2} 
    
  3. 如果你需要一個Clojure的地圖鍵集傳遞給Java方法,你可以使用

    (.keySet {:foo 1 :bar 2}) 
    ; => #< [:foo, :bar]> 
    
  4. 如果參與,保證所有的鍵是Comparable,這可以被利用爲difference高效計算與許多重要的地圖s(排序&合併掃描)。對於不受約束的鍵,這當然是不可行的,對於小地圖來說,它實際上可能會損害性能。

  5. 用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))))) 
+0

意外的答案Michal,非常感謝!關於1)只要一點,如果你指定nil等價於不存在(按照問題),我認爲可以通過將map-difference分配給需要「移除」的鍵來實現這一要求。 – mikera 2010-08-02 13:08:23

+0

我希望你知道defrecords?也許這是一個解決方案,如果你不需要地圖是通用的。 – nickik 2010-08-02 15:06:15

+0

@mikera:謝謝,很高興聽到這個消息。 :-)至於1),這是一個很好的觀點,它應該是對任何解決方案的小調整 - 感謝修正! @nickik:我不確定你的想法。你能詳細說明一下嗎? – 2010-08-02 15:13:21

3
  1. Clojure地圖會很好,因爲閱讀clojure地圖非常快。

  2. 我無法回答你,但我可以給你看看。 Brenton Ashworth做了一個測試工具,他用地圖比較來解決問題。也許你可以看看他的代碼來獲得實現提示。 http://formpluslogic.blogspot.com/2010/07/better-clojure-test-results-with-deview.htmlhttp://github.com/brentonashworth/deview

  3. 我不認爲這是一個更好的實現,比較鍵和查找,如果是不同的。

+0

請修正第三點的語法;它沒有任何意義。 – Svante 2010-08-02 11:45:56

+0

這兩個鏈接都死了 – sloth 2015-08-01 08:58:21

6

在Java中,Google Commons Collections只提供performant solution

+1

謝謝!這雖然比我所尋找的更一般(它產生了一整套地圖比較,而不是我正在尋找的簡單差異地圖),但這很有用 – mikera 2010-08-02 11:40:55

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

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