Java 8 streams group by 3 fields and aggregate by sum and count produce single line output

僤鯓⒐⒋嵵緔 提交于 2019-12-03 09:49:45

Precondition

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;
}

Summarizing way

you can summarizing items grouping by product by using 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()));
);

Merges Items with Same Product

First, you need adding a qty field in 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;
    }

}

then you can using Collectors#toMap to merges items with same product:

Collection<Item> summarized = items.stream().collect(Collectors.toMap(
        it -> it.product,
        Function.identity(),
        Item::add
)).values();

Finally

you can see both ways doing the same thing, but the second approach is easier to operates on a stream. and the tests of two ways I having checked in github, you can click and see more details: summarizing items & merge items ways.

Ole V.V.

Here’s the quick and dirty solution:

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

You will probably want to build your own classes for both the keys and the values of the result map. In any case, with your data and the above code the map contains four entries:

prod1-cat1-t3:  200,23  1
prod1-cat2-t1:  200,46  2
prod3-cat2-t1:  150,23  1
prod2-cat1-t2:   50,23  1

The sums are printed with comma as decimal point because my computer has Danish locale (you can pass a locale to String.format() to control the locale if you want).

Your friend is the combination of Collectors.collectingAndThen() and Collectors.summarizingDouble(). I took that from this answer.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!