2014-09-10 88 views
3

有沒有辦法在Java中深度合併地圖?我已經看到一些關於它的帖子,但大多數似乎解決方案似乎只處理一個級別的合併或者是乏味的。N級地圖的遞歸合併

我的數據結構(使用JSON字符串來表示地圖)類似於這樣:

{ name: "bob", emails: { home: "[email protected]", work : "[email protected]" } } 

理想的情況下,如果我有另一張圖像

{ emails: { home2: "[email protected]" } } 

後與第一地圖合併它看起來像

{ name: "bob", emails: { home: "[email protected]", work : "[email protected]", home2: "[email protected] } } 

我可以保證我所有的地圖都是<String, Object>。有沒有開箱即用的解決方案?真的,我試圖避免自己爲嵌套或大對象編寫一堆遞歸或迭代代碼。

+0

發生什麼情況,如果有衝突?你想如何處理?順便問一下好問題。 – 2014-09-10 20:22:50

+0

對於我自己,它是從左到右的地圖合併,右圖(新地圖)覆蓋舊地圖,如果密鑰存在.... – adrian 2014-09-10 20:25:06

回答

-1

我沒有深入的合併方法,但我很清楚,但這可能是設計。一般來說,嵌套Map是一種反模式,因爲它們很快變得難以管理。例如,假設你遞了一段代碼,看起來像這樣:

Map<String, Map<String, Map<String, Object>>> someNonsensicalMap = someObject.getNonsensicalMap(); 

好運製作的是什麼地圖包含不耗時的逆向工程的努力太大的意義。這種事情應該避免。

解決這個問題的常用方式是使用類而不是嵌套地圖來爲內容提供一些有意義的上下文。使用DOM解析XML時,一個真實世界的例子就會發揮作用。看看這個SO帖子的答案:parsing Xml with NodeList and DocumentBuilder。你可以看到,代替嵌套地圖,有一個對象,其中包含Element,它本身可以包含NodeList等,這更容易遵循並允許實際上無限嵌套。

因此,我強烈建議重新訪問您的設計並避免嵌套地圖。當使用類,而不是一個嵌套的地圖,那麼你可以添加合併一種或多種方法,以你的類來執行深合併:

class Person { 
    Set<String> names = new HashSet<String>(); 
    Set<String> emails = new HashSet<String>(); 

    public Set<String> getNames() { return names; } 
    public Set<String> getEmails() { return emails; } 

    public void mergeNames(HashSet<String> moreNames) { 
    names.addAll(moreNames); 
    } 

    public void mergeEmails(HashSet<String> moreEmails) { 
    emails.addAll(moreEmails); 
    } 

    public void mergeNames(HashSet<String> moreNames) { 
    names.addAll(moreNames); 
    } 

    public void mergePerson(Person person) { 
    mergeNames(person.getNames()); 
    mergeEmails(person.getEmails()); 
    } 
} 

以上是怎樣的一個簡單的例子,基於以上的JSON,但可以很容易擴展爲遞歸地合併類所包含的任何字段。

+0

我會同意你,除了有一個缺陷如果你需要重建數據嵌套的地圖形式。這可能是一個問題。 – adrian 2014-09-10 20:59:47

+0

你能舉一個例子說明你爲什麼需要嵌套地圖形式嗎?你的意思是說地圖需要被序列化/反序列化爲JSON或XML?因爲這也可以通過向類添加toJSON()和/或toXML()方法來處理,而不是使用嵌套的映射反模式。 – paulk23 2014-09-10 21:14:38

10

的改進版本:this gist

這是一種深合併的Java地圖:

// This is fancier than Map.putAll(Map) 
private static Map deepMerge(Map original, Map newMap) { 
    for (Object key : newMap.keySet()) { 
     if (newMap.get(key) instanceof Map && original.get(key) instanceof Map) { 
      Map originalChild = (Map) original.get(key); 
      Map newChild = (Map) newMap.get(key); 
      original.put(key, deepMerge(originalChild, newChild)); 
     } else if (newMap.get(key) instanceof List && original.get(key) instanceof List) { 
      List originalChild = (List) original.get(key); 
      List newChild = (List) newMap.get(key); 
      for (Object each : newChild) { 
       if (!originalChild.contains(each)) { 
        originalChild.add(each); 
       } 
      } 
     } else { 
      original.put(key, newMap.get(key)); 
     } 
    } 
    return original; 
} 

作品嵌套貼圖,物體和物體的名單。請享用。

(免責聲明:我不是Java開發人員!)

+0

你能解釋一下它是如何工作的嗎? @ wonderkid2 – 2018-01-13 12:50:12

2

我真的很喜歡上面wonderkid2的做法。

我修改了它多一點:

  1. 略微更高的性能(避免一些。得到(_)通過輸入)
  2. 遞歸任何收集調用,而不是僅僅列出
  3. 一些更嚴格的驗證

這不是一般的美妙的做法,但我看它如何被需要有時。

(PS我用番石榴,但這些電話(Objects.equals,Preconditions.checkArgument)是容易的,如果需要更換。)

@SuppressWarnings({"rawtypes", "unchecked"}) 
static void deepMerge(
     Map original, 
     Map newMap) { 

    for (Entry e : (Set<Entry>) newMap.entrySet()) { 
     Object 
      key = e.getKey(), 
      value = e.getValue(); 

     // unfortunately, if null-values are allowed, 
     // we suffer the performance hit of double-lookup 
     if (original.containsKey(key)) { 
      Object originalValue = original.get(key); 

      if (Objects.equal(originalValue, value)) 
       return; 

      if (originalValue instanceof Collection) { 
       // this could be relaxed to simply to simply add instead of addAll 
       // IF it's not a collection (still addAll if it is), 
       // this would be a useful approach, but uncomfortably inconsistent, algebraically 
       Preconditions.checkArgument(value instanceof Collection, 
         "a non-collection collided with a collection: %s%n\t%s", 
         value, originalValue); 

       ((Collection) originalValue).addAll((Collection) value); 

       return; 
      } 

      if (originalValue instanceof Map) { 
       Preconditions.checkArgument(value instanceof Map, 
         "a non-map collided with a map: %s%n\t%s", 
         value, originalValue); 

       deepMerge((Map) originalValue, (Map) value); 

       return; 
      } 

      throw new IllegalArgumentException(String.format(
        "collision detected: %s%n%\torig:%s", 
        value, originalValue)); 

     } else 
      original.put(key, value); 
    } 
} 
+2

而不是每個'if'語句中的'return',它應該是'continue'。如果您想覆蓋'original'映射中的現有鍵(而不是拋出'IllegalArgumentException',則將'IllegalArgumentException'替換爲'original.put(key,value);'。 – 2016-07-31 10:31:44

3

我使用jackson裝載JSON和yaml配置文件,有是每個環境的基本配置文件和一個配置文件。 我加載基本配置和環境特定配置。 然後我深度合併這兩個地圖。列表也被合併,刪除重複。 在map1上深度合併值,在發生衝突時map2中的值覆蓋來自map1的值。

void deepMerge(Map<String, Object> map1, Map<String, Object> map2) { 
    for(String key : map2.keySet()) { 
     Object value2 = map2.get(key); 
     if (map1.containsKey(key)) { 
      Object value1 = map1.get(key); 
      if (value1 instanceof Map && value2 instanceof Map) 
       deepMerge((Map<String, Object>) value1, (Map<String, Object>) value2); 
      else if (value1 instanceof List && value2 instanceof List) 
       map1.put(key, merge((List) value1, (List) value2)); 
      else map1.put(key, value2); 
     } else map1.put(key, value2); 
    } 
} 

List merge(List list1, List list2) { 
    list2.removeAll(list1); 
    list1.addAll(list2); 
    return list1; 
} 

例如: 基本配置:

electronics: 
    computers: 
    laptops: 
     apple: 
     macbook: 1000 
     macbookpro: 2000 
     windows: 
     surface: 2000 
    desktop: 
     apple: 
     imac: 1000 
     windows: 
     surface: 2000 
    phones: 
    android: 
     samsung: 
     motox: 300 
    apple: 
     iphone7: 500 

books: 
    technical: 
    - java 
    - perl 
    novels: 
    - guerra y paz 
    - crimen y castigo 
    poetry: 
    - neruda 
    - parra 

測試ENV配置:

electronics: 
    computers: 
    laptops: 
     windows: 
     surface: 2500 
    desktop: 100 
    phones: 
    windows: 
     nokia: 800 

books: 
    technical: 
    - f sharp 
    novels: [2666] 
    poetry: 
    - parra 

合併配置:

electronics: 
    computers: 
    laptops: 
     apple: 
     macbook: 1000 
     macbookpro: 2000 
     windows: 
     surface: 2500 
    desktop: 100 
    phones: 
    android: 
     samsung: 
     motox: 300 
    apple: 
     iphone7: 500 
    windows: 
     nokia: 800 
books: 
    technical: 
    - "java" 
    - "perl" 
    - "f sharp" 
    novels: 
    - "guerra y paz" 
    - "crimen y castigo" 
    - 2666 
    poetry: 
    - "neruda" 
    - "parra"