Vim*_*vam 5 java java-8 java-stream
我有以下格式的数据:
ProductName | Date
------------|------
ABC | 1-May
ABC | 1-May
XYZ | 1-May
ABC | 2-May
Run Code Online (Sandbox Code Playgroud)
它采用 List 的形式,其中 Product 由 ProductName 和 Date 组成。现在我想对这些数据进行分组并得到总和的计数,如下所示:
1-May
-> ABC : 2
-> XYZ : 1
-> Total : 3
2-May
-> ABC: 1
-> Total : 1
Run Code Online (Sandbox Code Playgroud)
到目前为止,我所取得的是分组计数,而不是总价值。
myProductList.stream()
.collect(Collectors.groupingBy(Product::getDate,
Collectors.groupingBy(Product::getProductName, Collectors.counting())));
Run Code Online (Sandbox Code Playgroud)
不知道如何获得总价值。
您可以使用Collectors.collectingAndThen将带有总计的条目添加到每个内部地图中:
Map<LocalDate, Map<String, Long>> result = myProductList.stream()
.collect(Collectors.groupingBy(
Product::getDate,
TreeMap::new, // orders entries by key, i.e. by date
Collectors.collectingAndThen(
Collectors.groupingBy(
Product::getProductName,
LinkedHashMap::new, // LinkedHashMap is mutable and
Collectors.counting()), // preserves insertion order, i.e.
map -> { // we can insert the total later
map.put("Total", map.values().stream().mapToLong(c -> c).sum());
return map;
})));
Run Code Online (Sandbox Code Playgroud)
该result地图包含:
{2017-05-01={ABC=2, XYZ=1, Total=3}, 2017-05-02={ABC=1, Total=1}}
Run Code Online (Sandbox Code Playgroud)
我已经为外部地图和内部地图指定了供应商。外部映射是 a TreeMap,它按键(在本例中按日期)对其条目进行排序。对于内部映射,我决定使用LinkedHashMap,它是可变的并保留插入顺序,即一旦内部映射填充了数据,我们就可以稍后插入总数。
到目前为止,一切都很好。不过,我认为我们可以做得更好,因为一旦每个内部映射都填充了数据,我们就需要遍历它的所有值来计算总数。(这就是map.values().stream().mapToLong(c -> c).sum()实际的情况)。通过这样做,我们没有利用这样一个事实:在计数时,流的每个元素1不仅添加到它所属的组中,而且还添加到总数中。幸运的是,我们可以使用自定义收集器来解决这个问题:
public static <T, K> Collector<T, ?, Map<K, Long>> groupsWithTotal(
Function<? super T, ? extends K> classifier,
K totalKeyName) {
class Acc {
Map<K, Long> map = new LinkedHashMap<>();
long total = 0L;
void accumulate(T elem) {
this.map.merge(classifier.apply(elem), 1L, Long::sum);
this.total++;
}
Acc combine(Acc another) {
another.map.forEach((k, v) -> {
this.map.merge(k, v, Long::sum);
this.total += v;
});
return this;
}
Map<K, Long> finish() {
this.map.put(totalKeyName, total);
return this.map;
}
}
return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::finish);
}
Run Code Online (Sandbox Code Playgroud)
该收集器不仅对每个组的元素进行计数(就像Collectors.groupingBy(Product::getProductName, Collectors.counting())这样做),而且在累加和组合时也会添加到总数中。完成后,它还会添加一个包含总计的条目。
要使用它,只需调用groupsWithTotal辅助方法:
Map<LocalDate, Map<String, Long>> result = myProductList.stream()
.collect(Collectors.groupingBy(
Product::getDate,
TreeMap::new,
groupsWithTotal(Product::getProductName, "Total")));
Run Code Online (Sandbox Code Playgroud)
输出是相同的:
{2017-05-01={ABC=2, XYZ=1, Total=3}, 2017-05-02={ABC=1, Total=1}}
Run Code Online (Sandbox Code Playgroud)
作为奖励,鉴于LinkedHashMap支持null键,此自定义收集器还可以按键进行分组null,即在极少数情况下 aProduct具有null productName,它将使用该null键创建一个条目而不是抛出 a NullPointerException。
| 归档时间: |
|
| 查看次数: |
4857 次 |
| 最近记录: |