Reputation: 1576
I have a class something like this
public class Item {
private String sku;
private int quantity;
private int amount;
private String txnId;
}
I have a list of these items and I want to fist group the items by txnId and then if the SKU of the 2 items are same I need add the quantity and the amount fields?
Item["productA",1,100,"ABC"]
Item["productB",2,200,"ABC"]
Item["productA",3,200,"ABC"]
Item["productB",2,200,"PQR"]
Item["productB",2,200,"PQR"]
Item["productA",2,200,"PQR"]
at the end I should be having
MAP["ABC",List<Item["productA",4,300,"ABC"],Item["productB",2,200,"ABC"],
"PQR",List<Item["productB",4,400,"PQR"],Item["productA",2,200,"PQR"]
I am able to group by the txnID
list.stream().collect(Collectors.groupingBy(txnID)
but I am not sure how I should check for the same SKU and if exists add the amount and quantity and get the map output.
Upvotes: 2
Views: 2428
Reputation: 1503
toMap
offers a good alternative to merge values in such cases.
Here's what another solution looks like:
var result = itemList.stream().collect(
groupingBy(Item::getTxnId,
collectingAndThen(toMap(Item::getSku, i -> i, Item::sum),
Map::values)));
// where Item::sum is a reference to a static method in class Item
static Item sum(Item i1, Item i2) {
return new Item(i1.getSku(),
i1.getQuantity() + i2.getQuantity(),
i1.getAmount() + i2.getAmount(),
i1.getTxnId());
}
Unlike groupingBy
(which takes a Function
and then a Collector
), collectingAndThen
takes a collector (here, it's toMap
) and then takes a Function
(here, Map::values
) that works on the result of the collector.
Upvotes: 2
Reputation: 135
There is a 2 argument groupingBy
method - the second argument is a Collector
that is applied to the value (a List
) associated to each key.
Let's do it step-by-step.
Assuming import static java.util.stream.Collectors.*;
and that we have the list:
List<Item> list = List.of(
new Item("A", 1, 100, "ABC"),
new Item("B", 2, 200, "ABC"),
new Item("A", 3, 300, "ABC"),
new Item("B", 2, 200, "PQR"),
new Item("B", 2, 200, "PQR"),
new Item("A", 2, 200, "PQR"));
txnId
as already in question:list.stream().collect(groupingBy(Item::txnId))
this will result in a Map<String,List<Item>>
:
{ PQR=[Item[sku=B, quantity=2, amount=200, txnId=PQR],
Item[sku=B, quantity=2, amount=200, txnId=PQR],
Item[sku=A, quantity=2, amount=200, txnId=PQR]
],
ABC=[Item[sku=A, quantity=1, amount=100, txnId=ABC],
Item[sku=B, quantity=2, amount=200, txnId=ABC],
Item[sku=A, quantity=3, amount=300, txnId=ABC]
]
}
sku
:list
.stream()
.collect(groupingBy(Item::txnId,
groupingBy(Item::sku)))
resulting in a Map<String,Map<String,List<Item>>
:
{ PQR={ A=[ Item[sku=A, quantity=2, amount=200, txnId=PQR] ],
B=[ Item[sku=B, quantity=2, amount=200, txnId=PQR],
Item[sku=B, quantity=2, amount=200, txnId=PQR] ]
},
ABC={ A=[ Item[sku=A, quantity=1, amount=100, txnId=ABC],
Item[sku=A, quantity=3, amount=300, txnId=ABC] ],
B=[ Item[sku=B, quantity=2, amount=200, txnId=ABC] ]
}
}
I will assume we have a sum
method to sum 2 Item
s returning a new one, very simplified:
Item {
// fields
// getters and setters
public static Item sum(Item item1, Item item2) {
return new Item(item2.sku, item1.quantity+item2.qantity, item1.amount+item2.amount, item2.txnId);
}
}
this could also be defined as a 1-arg non-static method
Now we can use reducing
to add up the elements of the internal list:
list
.stream()
.collect(groupingBy(Item::txnId,
groupingBy(Item::sku,
reducing(new Item(null, 0, 0, null),
Item::sum))))
finally resulting in:
{ PQR={ A=Item[sku=A, quantity=2, amount=200, txnId=PQR],
B=Item[sku=B, quantity=4, amount=400, txnId=PQR]
},
ABC={ A=Item[sku=A, quantity=4, amount=400, txnId=ABC],
B=Item[sku=B, quantity=2, amount=200, txnId=ABC]
}
}
Note1: A Lambda expression may be used instead of the sum
method for reducing
:
....reducing( new Item(null, 0, 0, null),
(i1,i2) -> new Item(i2.sku,
i1.quantity+i2.quantity,
i1.amount+i2.amount,
i2.txnId) )
Note2: To convert the inner Maps to a List use collectingAndThen
with Map::values
around the second groupingBy
:
... collectingAndThen( groupingBy(Item::sku, ...), Map:values ) ...
Upvotes: 2