Reputation: 113
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
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
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
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