I need to find the unique name count, and summation of a field value inside a list
Below is my POJO:
@Getter
@Setter
public class Orders {
private String dealId;
private String fieldId;
private String accountName;
private List<Demands> demands; //Demands contains multiple fields inside it, but I just need
//the 'amount' value mainly which is a BigDecimal.
}
List of Orders(ordersList) :
DealId | FieldId | AccountName | Demand |
1 | 11 | ABC | 100,100 |
1 | 11 | ABC | 200 |
1 | 11 | PQR | 300,100 |
1 | 12 | ABC | 100 |
2 | 21 | ABC | 200 |
2 | 21 | PQR | 300,500 |
2 | 21 | XYZ | 100 |
2 | 22 | ABC | 200,100 |
2 | 22 | ABC | 300 |
End Result :
DealId | FieldId | AccountNameCount | DemandSum |
1 | 11 | 2 | 800 |
1 | 12 | 1 | 100 |
2 | 21 | 3 | 1100 |
2 | 22 | 1 | 600 |
I need to take the end result in some POJO or collection in this format, where
AccountNameCount is the count of unique account names for a particular FieldId for a deal. DemandSum is the summation of the demand value for the particular FieldId under a DealId.
Note: Demand is a List inside the Orders model, so it may have multiple values, all those values for a particular FieldId under a DealId should be added.
For example : for DealId 1, with FieldId 11, there are 2 unique accountNames, and adding all demand values under that fieldid, and dealId = 800
I am trying this using Java 8 streams, have been able to group the list by dealId, and then the fieldId, but I am not sure how to calculate the unique account name on fieldId level, and the demand value summation, without affecting performance.
Map<String, Map<String, List<Orders>>> map = ordersList.stream()
.collect(Collectors.groupingBy(Orders::getDealId, Collectors.groupingBy(Orders::getFieldId)));
The Collectors.reducing option is available to add inside the stream to add demands value, but as the Demands is a list inside Orders, doesnt apply here.
Collectors.reducing(BigDecimal.ZERO, Demands::getAmount, BigDecimal::add);
Any help is appreciated!
CodePudding user response:
First, let's start with grouping by dealId
and fieldId
After that what we need is accountName
distinct count and sum of demands
, but those are available in List, which is inside Map
In order to generate stream on map we will use entrySet,
This will gives us Map.Entry<dealId, Map<fieldIs, List>>
To access key, value from Map.Entry we use methods getKey and getValue
// group by dealId and fieldId
Map<String, Map<String, List<Orders>>> map = ordersList.stream()
.collect(Collectors.groupingBy(Orders::getDealId, Collectors.groupingBy(Orders::getFieldId)));
List<OrdersSummary> result = map
.entrySet()
.stream()
.map(Test::getOrdersSummaries)
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(result);
private static List<OrdersSummary> getOrdersSummaries(Map.Entry<String, Map<String, List<Orders>>> entry) {
return entry.getValue()
.entrySet()
.stream()
.map(e2 -> toOrdersSummary(entry.getKey(), e2))
.collect(Collectors.toList());
}
private static OrdersSummary toOrdersSummary(String dealId, Map.Entry<String, List<Orders>> entry){
long accountNameCount= entry.getValue().stream().map(Orders::getAccountName).distinct().count();
BigDecimal demandSum = entry.getValue().stream().map(Orders::getDemands)
.flatMap(List::stream)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return new OrdersSummary(dealId, entry.getKey(), accountNameCount, demandSum);
}
Full code Demo
CodePudding user response:
Something like this, perhaps?
return orderList.stream()
.collect(Collectors.groupingBy(o -> Arrays.asList(o.dealId, o.fieldId)))
.entrySet().stream().map(e -> {
var stats = new OrderStats();
stats.dealId = e.getKey().get(0);
stats.fieldId = e.getKey().get(1);
stats.accountNameCount = e.getValue().stream()
.map(o -> o.accountName)
.distinct()
.count();
stats.demandSum = e.getValue().stream()
.flatMap(o -> o.demands.stream())
.map(d -> d.amount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return stats;
})
.collect(Collectors.toList());
I use a List
as key to avoid unnecessary nesting in the grouping Map
. This works because List
implements content-based equality.
CodePudding user response:
Although you can get result by java 8 stream API (maybe unreadable a bit). but there are another collector(teeing) provided after java 12 that helps you to write this in more readable way.
public static Collector teeing (Collector downstream1,
Collector downstream2,
BiFunction merger);
solution: you need to create some helper objects such as Detail
, Result
orders.stream()
.collect(groupingBy(order -> Arrays.asList(order.getDealId(), order.getFieldId()),
teeing(
// unique account names for each group
mapping(Orders::getAccountName, Collectors.toSet()),
// sum all demands amount for each group
collectingAndThen(mapping(Orders::getDemands, Collectors.toList()),
demands -> demands.stream()
.flatMap(List::stream)
.map(Demands::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add)),
// create an object by previous results
(names, demandSum) -> new Detail(names.size(), demandSum)
)
)).entrySet().stream()
.map(entry -> new Result(
entry.getKey().get(0),
entry.getKey().get(1),
entry.getValue().getCountByAccountName(),
entry.getValue().getDemandSum()))
.collect(Collectors.toList());