2

I have object PayBill, that contains list of items. Object Item contains article. This article is connected to another Nomenclature object, which is stored as db table with next structure:

Article ProductGroup
------- ------------
010101  Telephone
040444  Computer

I parsed my transaction list into object Stat with next attributes: payBillNumber, productGroup, amountOfItems, sumPriceOfItems. Now list of Stat looks like:

BillNumber ProductGroup NumberOfItemsInBill SummaryItemsCostInBill
---------- ------------ ------------------- ----------------------
1          Telephone    1                   1000
1          Computer     1                   200
1          Telephone    2                   2000
1          Computer     1                   200
2          Accessories  1                   1500

How can I collapse this List<Stat> into new List<Stat> with following view:

BillNumber ProductGroup NumberOfItemsInBill SummaryItemsCostInBill
---------- ------------ ------------------- ----------------------
    1          Telephone    3                   3000
    1          Computer     2                   400
    2          Accessories  1                   1500

I want my data to be grouped by BillNumber and ProductGroup with amount an price summing. Is it possible to do without creating new Map?

1
  • Take a look at this baeldung blog. There is the groupby Collector descriped. Maybe it helps you with your problem. Commented Mar 17, 2022 at 8:55

3 Answers 3

2

I have come up with a solution involving intermediate maps, but in a functional way using groupingBy and reducing:

List<Stat> collect = stats.stream()
    .collect(Collectors.groupingBy(Stat::getBillNumber, Collectors.groupingBy(Stat::getProductGroup,
            Collectors.reducing((left, right) -> new Stat(
                    left.getBillNumber(), 
                    left.getProductGroup(), 
                    left.getNumberOfItemsInBill() + right.getNumberOfItemsInBill(),
                    left.getSummaryItemsCostInBill() + right.getSummaryItemsCostInBill()
            )))))
    .values().stream()
    .flatMap(m -> m.values().stream())
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

Note that this results in a lot of Stat objects being created. If performance is an issue, you can come up with some aggregator and use Collectors.of instead of Collectors.reducing or stick to reducing and map it at the end.

Sign up to request clarification or add additional context in comments.

Comments

1

You'll have to create the Stat constructor, but it goes like this:

Collection<Stat> aggregated = stats.stream()
  .collect(Collectors.toMap(
    stat -> new AbstractMap.SimpleEntry<>(stat.getProductGroup(), stat.getBillNumber()),
    stat -> stat,
    (stat1, stat2) -> new Stat(stat1.getBillNumber(), stat1.getProductGroup(), stat1.getNumberOfItemsInBill() + stat2.getNumberOfItemsInBill(), stat1.getSummaryItemsCostInBill() + stat2.getSummaryItemsCostInBill()
  ))
  .values();

3 Comments

this doesn't group the items, as they need to be grouped by BillNumber and ProductGroup
I corrected the answer since I didn't notice the group by BillNumber. For 2 fields you can use SimpleEntry. With more grouping fields, you'll need to create a StatGrouping class, with grouping fields, and have equals/hashcode implemented properly.
@VinhTruong thanks, also worked) Didn't use SimpleEntry too much, so just forgot about such option. Looks like very clean solution
1

I created the below class to represent data in sort form.

@Data
class Stat{
    int bn;
    String pg;
    int noOfBills;
    int cost;
    public Stat(int bn, String pg, int noOfBills, int cost)
    {
        this.bn = bn;
        this.pg = pg;
        this.noOfBills = noOfBills;
        this.cost = cost;
    }

    @Override
    public String toString()
    {
        return this.bn + " - " + this.pg + " - " + this.noOfBills + " - " + this.cost;
    }
}

And here the main method with sample data i tested, you can collect instead of printing.

public static void main(String[] args)
    {
        List<Stat> list = new ArrayList<>();
        list.add(new Stat(1, "Telephone",1, 1000));
        list.add(new Stat(1, "Computer",1, 200));
        list.add(new Stat(1, "Telephone",2, 2000));
        list.add(new Stat(1, "Computer",1, 200));
        list.add(new Stat(2, "Accessories",1, 1500));
        
        list.stream().collect(toMap(Stat::getPg, Function.identity(),
                        (a, b) -> new Stat(a.getBn(), a.getPg(), a.getNoOfBills() + b.getNoOfBills(), a.getCost() + b.getCost())))
                .forEach((id,foo)->System.out.println(foo));
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.