如何为不包含重复项的项指定特定逻辑,如何连接两个Java流?

时间:2018-03-09 00:43:15

标签: java collections java-8 java-stream

我有两个使用相同对象作为键的地图。我想按键合并这两个流。当两个地图中都存在一个键时,我希望生成的地图能够运行一个公式。当一个键存在于单个映射中时,我希望该值为0.

Map<MyKey, Integer> map1;
Map<MyKey, Integer> map2;

<Map<MyKey, Double> result =
    Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
        .collect(Collectors.toMap(
            Map.Entry::getKey, Map.Entry::getValue,
            (val1, val2) -> (val1 / (double)val2) * 12D));

如果两个地图中都存在密钥,这将使用公式,但我需要一种简单的方法来设置仅存在于两个地图之一中的密钥值为0D。

我可以通过设置数学并尝试计算两个keySet的内连接,然后从它们的完全外连接中减去内连接结果来做到这一点......但这是很多工作感到没必要。

有没有更好的方法,或者我可以轻松使用Streaming API做什么?

3 个答案:

答案 0 :(得分:6)

这是一种简单的方法,只流式传输键,然后查找值,并保持原始地图不变。

Map<String, Double> result =
    Stream.concat(map1.keySet().stream(), map2.keySet().stream())
          .distinct()
          .collect(Collectors.toMap(k -> k, k -> map1.containsKey(k) && map2.containsKey(k)
                                                 ? map1.get(k) * 12d / map2.get(k) : 0d));

测试

Map<String, Integer> map1 = new HashMap<>();
Map<String, Integer> map2 = new HashMap<>();
map1.put("A", 1);
map1.put("B", 2);
map2.put("A", 3);
map2.put("C", 4);
// code above here
result.entrySet().forEach(System.out::println);

输出

A=4.0
B=0.0
C=0.0

答案 1 :(得分:4)

要使此解决方案正常工作,您的初始地图应为Map<MyKey, Double>。如果值最初为Integer,我会尝试找到另一种解决方案。

你甚至不需要溪流!您应该只需使用Map#replaceAll修改其中一个Map

map1.replaceAll((k, v) -> map2.containsKey(k) ? 12D * v / map2.get(k) : 0D);

现在,您只需要将map1的每个密钥添加到map2 ,而不是map1

map2.forEach((k, v) -> map1.putIfAbsent(k, 0D));

如果您不想修改Map中的任何一个,那么您应首先创建map1的深层副本。

答案 2 :(得分:2)

Stream.concat这里不是正确的方法,因为你将两张地图的元素放在一起,因此需要将它们分开。

您可以通过应用函数并以不同方式处理其他键来直接执行处理键交集的预期任务来简化此操作。例如。当您在一个地图上流而不是两个地图的连接时,您只需检查其他地图中是否存在要么应用该功能,要么使用零。然后,仅在第二个映射中出现的键需要在第二步中置零:

Map<MyKey, Double> result = map1.entrySet().stream()
    .collect(Collectors.collectingAndThen(
        Collectors.toMap(Map.Entry::getKey, e -> {
            Integer val2 = map2.get(e.getKey());
            return val2==null? 0.0: e.getValue()*12.0/val2;
        }),
        m -> {
            Map<MyKey, Double> rMap = m.getClass()==HashMap.class? m: new HashMap<>(m);
            map2.keySet().forEach(key -> rMap.putIfAbsent(key, 0.0));
            return rMap;
        }));

这显然是因为Streams不提供处理地图条目的便利方法。此外,我们必须处理第二个处理步骤的未指定的地图类型。如果我们提供了地图供应商,我们还必须提供合并功能,使代码更加冗长。

更简单的解决方案是使用Collection API而不是Stream API:

Map<MyKey, Double> result = new HashMap<>(Math.max(map1.size(),map2.size()));
map2.forEach((key, value) -> result.put(key, map1.getOrDefault(key, 0)*12D/value));
map1.keySet().forEach(key -> result.putIfAbsent(key, 0.0));

这显然不那么冗长,而且可能更有效,因为它省略了Stream解决方案的一些处理步骤,并为地图提供了正确的初始容量。如果我们使用零作为缺席键的第一个映射值的默认值,它会利用公式求值为所需的零结果的事实。如果您想使用不具有此属性的其他公式或者想要避免计算缺少的映射,则必须使用

Map<MyKey, Double> result = new HashMap<>(Math.max(map1.size(),map2.size()));
map2.forEach((key, value2) -> {
    Integer value1 = map1.get(key);
    result.put(key, value1 != null? value1*12D/value2: 0.0);
});
map1.keySet().forEach(key -> result.putIfAbsent(key, 0.0));