2016-01-04 56 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)。 –