Java 8 个流按 3 个字段分组并按总和和计数聚合产生单行输出
Java 8 streams group by 3 fields and aggregate by sum and count produce single line output
我知道论坛上也有人问过类似的问题,但 none 似乎完全解决了我的问题。现在我是 Java 8 的新手,所以请多多包涵。
我有一个产品列表,例如:
Input:
name category type cost
prod1 cat2 t1 100.23
prod2 cat1 t2 50.23
prod1 cat1 t3 200.23
prod3 cat2 t1 150.23
prod1 cat2 t1 100.23
Output:
Single line (name, category, type) summing the cost and count of products.
Product {
public String name;
public String category;
public String type;
public int id;
public double cost;
}
我需要按名称、类别和类型对其进行分组,并生成一个结果
汇总此数据并生成每种产品的总成本和数量。大多数示例显示按两个字段分组并使用单一条件聚合。
根据论坛上的建议,我想到了这个分组:
public class ObjectKeys {
ArrayList<Object> keys;
public ObjectKeys(Object...searchKeys) {
keys = new ArrayList<Object>();
for (int i = 0; i < searchKeys.length; i++) {
keys.add( searchKeys[i] );
}
}
}
然后使用如下:
Map<String, Map<String, Map<String, List<Product>>>> productsByNameCategoryType =
products.stream().collect(groupingBy(new ObjectKeys(l.name(), l.category(),l.type())))
但是我如何对上面的代码进行计数和求和呢?特别是对于具有 2 个以上字段的分组。
有一个更好的方法吗?
就像我提到的我的 Java8 不太好,请帮忙。
这是快速而肮脏的解决方案:
Map<String, String> productsByNameCategoryType = products.stream()
.collect(Collectors.groupingBy(p
-> p.getName() + '-' + p.getCategory() + '-' + p.getType(),
Collectors.collectingAndThen(
Collectors.summarizingDouble(Product::getCost),
dss -> String.format("%7.2f%3d",
dss.getSum(), dss.getCount()))));
您可能希望为结果映射的键和值构建您自己的 类。无论如何,使用您的数据和上述代码,地图包含四个条目:
prod1-cat1-t3: 200,23 1
prod1-cat2-t1: 200,46 2
prod3-cat2-t1: 150,23 1
prod2-cat1-t2: 50,23 1
总和以逗号作为小数点打印,因为我的计算机使用丹麦语言环境(如果需要,您可以将语言环境传递给 String.format()
以控制语言环境)。
您的朋友是 Collectors.collectingAndThen()
和 Collectors.summarizingDouble()
的组合。我从 .
先决条件
class Product {
public String name;
public String category;
public String type;
public int id;
//todo:implement equals(), toString() and hashCode()
}
class Item{
public Product product;
public double cost;
}
总结方式
您可以使用 Collectors#groupingBy & Collectors#summarizingDouble.
按产品分组汇总项目
List<Item> items = ...;
Map<Product, DoubleSummaryStatistics> stat = items.stream().collect(groupingBy(
it -> it.product,
Collectors.summarizingDouble(it -> it.cost)
));
// get some product summarizing
long count = stat.get(product).getCount();
double sum = stat.get(product).getSum();
//list all product summarizing
stat.entrySet().forEach(it ->
System.out.println(String.format("%s - count: %d, total cost: %.2f"
, it.getKey(),it.getValue().getCount(), it.getValue().getSum()));
);
将项目与同一产品合并
首先,您需要在 Item
class 中添加一个 qty
字段:
class Item{
public int qty;
//other fields will be omitted
public Item add(Item that) {
if (!Objects.equals(this.product, that.product)) {
throw new IllegalArgumentException("Can't be added items"
+" with diff products!");
}
return from(product, this.cost + that.cost, this.qty + that.qty);
}
private static Item from(Product product, double cost, int qty) {
Item it = new Item();
it.product = product;
it.cost = cost;
it.qty = qty;
return it;
}
}
然后您可以使用 Collectors#toMap 合并具有相同产品的项目:
Collection<Item> summarized = items.stream().collect(Collectors.toMap(
it -> it.product,
Function.identity(),
Item::add
)).values();
终于
你可以看到两种方式做同样的事情,但第二种方式更容易在流上操作。以及我在github中签到的两种方式的测试,你可以点击查看更多详情:summarizing items & merge items种方式。
我知道论坛上也有人问过类似的问题,但 none 似乎完全解决了我的问题。现在我是 Java 8 的新手,所以请多多包涵。 我有一个产品列表,例如:
Input:
name category type cost
prod1 cat2 t1 100.23
prod2 cat1 t2 50.23
prod1 cat1 t3 200.23
prod3 cat2 t1 150.23
prod1 cat2 t1 100.23
Output:
Single line (name, category, type) summing the cost and count of products.
Product {
public String name;
public String category;
public String type;
public int id;
public double cost;
}
我需要按名称、类别和类型对其进行分组,并生成一个结果 汇总此数据并生成每种产品的总成本和数量。大多数示例显示按两个字段分组并使用单一条件聚合。
根据论坛上的建议,我想到了这个分组:
public class ObjectKeys {
ArrayList<Object> keys;
public ObjectKeys(Object...searchKeys) {
keys = new ArrayList<Object>();
for (int i = 0; i < searchKeys.length; i++) {
keys.add( searchKeys[i] );
}
}
}
然后使用如下:
Map<String, Map<String, Map<String, List<Product>>>> productsByNameCategoryType =
products.stream().collect(groupingBy(new ObjectKeys(l.name(), l.category(),l.type())))
但是我如何对上面的代码进行计数和求和呢?特别是对于具有 2 个以上字段的分组。 有一个更好的方法吗?
就像我提到的我的 Java8 不太好,请帮忙。
这是快速而肮脏的解决方案:
Map<String, String> productsByNameCategoryType = products.stream()
.collect(Collectors.groupingBy(p
-> p.getName() + '-' + p.getCategory() + '-' + p.getType(),
Collectors.collectingAndThen(
Collectors.summarizingDouble(Product::getCost),
dss -> String.format("%7.2f%3d",
dss.getSum(), dss.getCount()))));
您可能希望为结果映射的键和值构建您自己的 类。无论如何,使用您的数据和上述代码,地图包含四个条目:
prod1-cat1-t3: 200,23 1
prod1-cat2-t1: 200,46 2
prod3-cat2-t1: 150,23 1
prod2-cat1-t2: 50,23 1
总和以逗号作为小数点打印,因为我的计算机使用丹麦语言环境(如果需要,您可以将语言环境传递给 String.format()
以控制语言环境)。
您的朋友是 Collectors.collectingAndThen()
和 Collectors.summarizingDouble()
的组合。我从
先决条件
class Product {
public String name;
public String category;
public String type;
public int id;
//todo:implement equals(), toString() and hashCode()
}
class Item{
public Product product;
public double cost;
}
总结方式
您可以使用 Collectors#groupingBy & Collectors#summarizingDouble.
按产品分组汇总项目List<Item> items = ...;
Map<Product, DoubleSummaryStatistics> stat = items.stream().collect(groupingBy(
it -> it.product,
Collectors.summarizingDouble(it -> it.cost)
));
// get some product summarizing
long count = stat.get(product).getCount();
double sum = stat.get(product).getSum();
//list all product summarizing
stat.entrySet().forEach(it ->
System.out.println(String.format("%s - count: %d, total cost: %.2f"
, it.getKey(),it.getValue().getCount(), it.getValue().getSum()));
);
将项目与同一产品合并
首先,您需要在 Item
class 中添加一个 qty
字段:
class Item{
public int qty;
//other fields will be omitted
public Item add(Item that) {
if (!Objects.equals(this.product, that.product)) {
throw new IllegalArgumentException("Can't be added items"
+" with diff products!");
}
return from(product, this.cost + that.cost, this.qty + that.qty);
}
private static Item from(Product product, double cost, int qty) {
Item it = new Item();
it.product = product;
it.cost = cost;
it.qty = qty;
return it;
}
}
然后您可以使用 Collectors#toMap 合并具有相同产品的项目:
Collection<Item> summarized = items.stream().collect(Collectors.toMap(
it -> it.product,
Function.identity(),
Item::add
)).values();
终于
你可以看到两种方式做同样的事情,但第二种方式更容易在流上操作。以及我在github中签到的两种方式的测试,你可以点击查看更多详情:summarizing items & merge items种方式。