'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?
Solution 1:[1]
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.
Solution 2:[2]
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();
Solution 3:[3]
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));
}
- Here I am using two argument groupingBy
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Andronicus |
| Solution 2 | |
| Solution 3 | Ketan Suthar |
