分组,按类型求和,然后使用 Java 流获取差异

Group, Sum byType then get diff using Java streams

我想用stream来分组,按类型求和,然后按类型求差的结果。

这是我的数据集。

Sample(SampleId=1, SampleTypeId=1, SampleQuantity=5, SampleType=ADD), 
Sample(SampleId=2, SampleTypeId=1, SampleQuantity=15, SampleType=ADD), 
Sample(SampleId=3, SampleTypeId=1, SampleQuantity=25, SampleType=ADD), 
Sample(SampleId=4, SampleTypeId=1, SampleQuantity=5, SampleType=SUBTRACT), 
Sample(SampleId=5, SampleTypeId=1, SampleQuantity=25, SampleType=SUBTRACT) 
Sample(SampleId=6, SampleTypeId=2, SampleQuantity=10, SampleType=ADD), 
Sample(SampleId=7, SampleTypeId=2, SampleQuantity=20, SampleType=ADD), 
Sample(SampleId=8, SampleTypeId=2, SampleQuantity=30, SampleType=ADD), 
Sample(SampleId=9, SampleTypeId=2, SampleQuantity=15, SampleType=SUBTRACT), 
Sample(SampleId=10, SampleTypeId=2, SampleQuantity=35, SampleType=SUBTRACT)

我目前的做法是这样的:

Map<Long, Integer> result = sampleList.stream()
.collect(
    Collectors.groupingBy(
        Sample::getSampleTypeId, 
        Collectors.summingInt(Sample::getSampleQuantity)
        )
    );

我的结果是这样的(SampleTypeId,结果):

{1=75, 2=110}

我不确定如何从这里继续。基本上,有一个 ADD 或 SUBTRACT 的 SampleType。所以我需要对所有 TypeId=1 和 Type=ADD 求和,然后对所有 typeId=1 和 Type=SUBSTRACT 求和,然后找出差异。

例子

1 >> (5+15+25) - (5+25) = 15
2 >> (10+20+30) - (15+35) = 10

所以预期的输出应该是{1=15, 2=10}

收集器是 Java8 中一个非常强大的功能,因为每个收集器都可以由不同的块组成,即便如此,也有一种简单的方法来组成收集器。

这个解决方案就是这样Collector组合解决的,下面我将引导您完成解决方案的步骤。

首先,我们从第一个要求开始:结果必须是一个以 TypeId:

为关键字的字典
Map<TypeId, List<Sample>> r = stream.collect(
  Collectors.groupingBy(Sample::getTypeId)
);

其次,在每个 TypeId 列表中,我们需要通过 Type 制作 sub-groups。为此,我们使用了 Grouping By 收集器的扩展版本,它允许我们指定在 class 化元素并将它们分配给组后我们应该做什么。在这种情况下,我们需要再次 class 验证它们:

Map<TypeId, Map<Type, List<Sample>>> r = stream.collect(
  groupingBy(Sample::getTypeId,
    groupingBy(Sample::getType))
);

现在,一旦两个 classifier 都完成了,我们需要对组内所有元素的数量求和。技术是相同的:我们告诉在 classifier 是 运行 之后如何处理组元素。在这种情况下,将所有金额相加:

Map<TypeId, Map<Type, Integer>> r = stream.collect(
  groupingBy(Sample::getTypeId,
    groupingBy(Sample::getType, Collectors.summingInt(Sample::getQuantity))
  )
);

接下来,最有趣的部分:我们需要对Map<Type, Integer>进行操作。更具体地说,我们需要做 map[Add] - map[Sub]。这一步最重要的概念是我们希望在所有其他收集操作完成后,在 returning 结果之前执行此操作。这是一个只能发生在名为 Collector.finisher 的流集合阶段的操作。我们可以通过调用 Collectors class:

collectingAndThen 方法来组合任意收集器的完成器
Map<TypeId, Integer> r = stream.collect(
  groupingBy(Sample::getTypeId,
    Collectors.collectingAndThen(
      groupingBy(Sample::getType, summingInt(Sample::getQuantity)),
      map -> map.getOrDefault(SampleType.ADD, 0) - map.getOrDefault(SampleType.SUBTRACT, 0)
    )
  )
);

就是这样。请注意,调用 getOrDefault 而不是 get 很重要,因为可能没有任何元素具有某些特定的 SampleType,否则 map 会 return null, 导致整个事情崩溃 NullPointerException.

sampleList.stream()
        .collect(Collectors.groupingBy(Sample::getSampleTypeId,
                Collectors.collectingAndThen(Collectors.groupingBy(Sample::getSampleType,
                        Collectors.summingInt(Sample::getSampleQuantity)),
                        map -> (map.getOrDefault(SampleType.ADD, 0) 
                                - map.getOrDefault(SampleType.SUBTRACT, 0)))));

groupingBy 分组 sampleTypeId。 在下一层,它涉及以键 SampleType 和键值 SampleQuantity 的总和来构造映射。 collectingAndThenfinisher减去ADD和SUBTRACT的值得到最后的结果。

版本 2(更简单):

sampleList.stream()
        .collect(Collectors.groupingBy(Sample::getSampleTypeId,
                Collectors.summingInt(sample -> sample.getSampleType() == SampleType.SUBTRACT
                        ? -sample.getSampleQuantity() :
                        sample.getSampleQuantity()
                )));

它不构建之前构建的第二张地图。它只是简单地检查 SampleType 是 ADD 还是 SUBTRACT 以及 returns sampleQuantity 是否适当。

可能有更简单的方法,只用地图:

Map<Long, Integer> result = sampleList.stream()
.map(sample->{if(sample.getSampleType==SUBTRACT)
     return new Sample((sample.SampleId, 
                        sample.SampleTypeId, 
                        sample.SampleQuantity*(-1), 
                        sample.SampleType))

     return sample;
 })
.collect(
    Collectors.groupingBy(
        Sample::getSampleTypeId, 
        Collectors.summingInt(Sample::getSampleQuantity)
        )
    );

以下:

1 >> ( 5 + 15 + 25) - ( 5 + 25) = 15
2 >> (10 + 20 + 30) - (15 + 35) = 10

与此完全相同:

1 >>  5 + 15 + 25 -  5 - 25 = 15
2 >> 10 + 20 + 30 - 15 - 35 = 10

所以你可以利用这一点,避免 nested-grouping by SampleType。相反,只需在下游收集器上求和或减去:

Map<Long, Integer> result = sampleList.stream()
    .collect(Collectors.groupingBy(
        Sample::getSampleTypeId, 
        Collectors.summingInt(sample -> sample.getSampleQuantity() * 
                (SampleType.ADD.equals(sample.getSampleType()) ? 1 : -1))));

更好的是,如果您可以在 Sample class:

中创建一个方法
public int getCalculatedQuantity() {
    return sampleQuantity * (SampleType.ADD.equals(sampleType) ? 1 : -1);
}

然后使用如下:

Map<Long, Integer> result = sampleList.stream()
    .collect(Collectors.groupingBy(
        Sample::getSampleTypeId, 
        Collectors.summingInt(Sample::getCalculatedQuantity)));