2016-01-04 81 views
4

我有一個嵌套映射列表(List<Map<String, Map<String, Long>>>),目標是將列表縮小爲單個映射,並且要完成合並如下:如果map1包含x->{y->10, z->20}map2包含x->{y->20, z->20}那麼這兩個應合併爲x->{y->30, z->40}使用Java 8流操作符將兩個級別映射的列表縮減爲單個兩級映射

我試圖做到這一點,這是工作正常。

import java.io.IOException; 
import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import java.util.Map.Entry; 
import java.util.function.BinaryOperator; 
import java.util.stream.Collectors; 

public class Test { 
    public static void main(String args[]) throws IOException { 
     Map<String, Map<String, Long>> data1 = new HashMap<>(); 
     Map<String, Long> innerData1 = new HashMap<>(); 
     innerData1.put("a", 10L); 
     innerData1.put("b", 20L); 
     data1.put("x", innerData1); 
     Map<String, Long> innerData2 = new HashMap<>(); 
     innerData2.put("b", 20L); 
     innerData2.put("a", 10L); 
     data1.put("x", innerData1); 

     Map<String, Map<String, Long>> data2 = new HashMap<>(); 
     data2.put("x", innerData2); 

     List<Map<String, Map<String, Long>>> mapLists = new ArrayList<>(); 
     mapLists.add(data1); 
     mapLists.add(data2); 

     Map<String, Map<String, Long>> result = mapLists.stream().flatMap(map -> map.entrySet().stream()). 
     collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, new BinaryOperator<Map<String, Long>>() { 

      @Override 
      public Map<String, Long> apply(Map<String, Long> t, 
        Map<String, Long> u) { 
       Map<String, Long> result = t; 
       for(Entry<String, Long> entry: u.entrySet()) { 
        Long val = t.getOrDefault(entry.getKey(), 0L); 
        result.put(entry.getKey(), val + entry.getValue()); 
       } 
       return result; 
      } 
     })); 
    } 
} 

有沒有其他更好的和有效的方法來解決這個問題?

如果嵌套級別大於2,如何更乾淨地做到這一點?假設列表類似List<Map<String, Map<String, Map<String, Long>>>>,我們必須將其減少到單個Map<String, Map<String, Map<String, Long>>>,假設類似於上面的合併功能。

回答

3

您有一般想法,只是可以簡化將兩個圖合併在一起的過程。 Merging the two maps can be done easily with

Map<String, Integer> mx = new HashMap<>(m1); 
m2.forEach((k, v) -> mx.merge(k, v, Long::sum)); 

此代碼從m1創建的合併地圖mx,然後在第二個地圖m2的所有條目進行迭代,並且合併每次進入mxMap.merge(key, value, remappingFunction)的幫助:這種方法將增加與給定的密鑰如果該鍵不存在映射,則返回給定值;否則,將使用給定的重映射函數重映射該鍵的現有值和給定值。在我們的例子中,重映射函數應該將兩個值相加在一起。

代碼:

Map<String, Map<String, Long>> result = 
    mapLists.stream() 
      .flatMap(m -> m.entrySet().stream()) 
      .collect(Collectors.toMap(
       Map.Entry::getKey, 
       Map.Entry::getValue, 
       (m1, m2) -> { 
        Map<String, Long> mx = new HashMap<>(m1); 
        m2.forEach((k, v) -> mx.merge(k, v, Long::sum)); 
        return mx; 
       } 
      )); 

如果有更多的 「水平」,你可以定義一個merge方法:

private static <K, V> Map<K, V> merge(Map<K, V> m1, Map<K, V> m2, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { 
    Map<K, V> mx = new HashMap<>(m1); 
    m2.forEach((k, v) -> mx.merge(k, v, remappingFunction)); 
    return mx; 
} 

和遞歸地使用它。例如,合併兩個Map<String, Map<String, Long>>m1m2,你可以使用

merge(m1, m2, (a, b) -> merge(a, b, Long::sum)); 

爲重映射功能Collectors.toMap

2

使用我的StreamEx庫:

Map<String, Map<String, Long>> result = StreamEx.of(mapLists) 
     .flatMapToEntry(m -> m) 
     .toMap((m1, m2) -> EntryStream.of(m1).append(m2).toMap(Long::sum)); 

flatMapToEntry中間操作變平圖轉換成EntryStream<String, Map<String, Long>>延伸Stream<Map.Entry<String, Map<String, Long>>>。終端操作僅使用提供的合併功能從條目流創建映射。要合併兩張地圖,我們再次使用EntryStream

+1

StreamEx通常更簡單的方法:)。 – Tunaki

+2

@Tunaki實際上會更簡單些[JDK-8072718](https://bugs.openjdk.java.net/browse/JDK-8072718)。 –