Evillain
Evillain

Reputation: 113

Group by arraylist of objects without creating map

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?

Upvotes: 2

Views: 777

Answers (3)

Ketan Suthar
Ketan Suthar

Reputation: 395

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

Upvotes: 1

Vinh Truong
Vinh Truong

Reputation: 431

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

Upvotes: 1

Andronicus
Andronicus

Reputation: 26026

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.

Upvotes: 2

Related Questions