Lambda – the Java 8 stream is composed of three fields and aggregated by sum and count to produce single line output
I know that similar problems have been raised in the forum, but none of them seem to have completely solved my problem Now I'm new to Java 8, so please wait patiently
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; }
I need to group this by name, category and type, and generate a result that summarizes the data and produces the total cost and quantity of each product Most examples show grouping by two fields and aggregating using a single condition
For the suggestion of forumn, I came up with this grouping:
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] ); } } }
Then use it as follows:
Map<String,Map<String,List<Product>>>> productsByNameCategoryType = products.stream().collect(groupingBy(new ObjectKeys(l.name(),l.category(),l.type())))
But how do I link counts and sums to the code above? Especially for groups in more than 2 fields Is there a better way?
As I mentioned, my java 8 is not so good, please help
Solution
premise
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; }
Summary method
You can use collectors #groupingby & amp;; Summarize items grouped by product 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())); );
Merge items with the same product
First, you need to add a qty field in the 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 use collectors #tomap to merge items with the same product:
Collection<Item> summarized = items.stream().collect(Collectors.toMap( it -> it.product,Function.identity(),Item::add )).values();
last
You can see two ways to do the same thing, but the second method is easier to run on the stream As well as the two tests I checked on GitHub, you can click to see more details: summarizing items & Merge items