lambda - Java 8 流按 3 个字段分组并按总和和计数聚合生成单行输出

标签 lambda java-8 java-stream chaining collectors

我知道论坛上有类似的问题,但似乎没有一个能完全解决我的问题。现在我对 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 个字段的 group by。
有一个更好的方法吗?

就像我提到的我的 Java8 不是那么好,请帮忙。

最佳答案

前提

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()));
);

合并具有相同产品的项目

首先,您需要添加一个 qty字段在 Item类(class):
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方法。

关于lambda - Java 8 流按 3 个字段分组并按总和和计数聚合生成单行输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44089193/

相关文章:

java - 如何在 map 中保存流的顺序

c++ - 适用于各种仿函数而无需强制转换的函数包装器

c++ - 将指向 lambda 函数的指针分配给指向另一个 lambda 函数的指针

kotlin - 根据传入的状态类类型构成 UI 操作的层次结构

java - 使用 Lambda 将列表项返回为字符串

java - 每天对 Java 8 Stream API 中的实体进行延迟排序?

lambda - 如何使用 Java 8 Lambda 表达式检查列表的正确顺序?

java-8 - Guava 可选到 Java 8 可选 多种选择

java - 对集合集合求和的最佳性能方法

java - 将 Set<String> 中的所有值分配给带有流的 Map<String, String>