分组,按类型求和,然后使用 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
的总和来构造映射。 collectingAndThen
的finisher减去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)));
我想用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
的总和来构造映射。 collectingAndThen
的finisher减去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)));