'Aggregating values in a list by multiple group by and calculating percentage of distribution in java 8
I have use case like this where I need to aggregate values in a list by multiple group by but then calculate percentage of distribution of each of those values and create a new list.
An example of list of items:
week1 source1 destination1 100
week1 source1 destination2 200
week1 source2 destination1 200
week1 source2 destination2 100
week2 source1 destination1 200
week2 source1 destination2 200
From this I want to group by week and source and calculate the total quantity and then distribute percentage based on quantity.
As an example the total quantity for week 1 from source 1 is 300, which is going to destination 1(100) and destination 2(200). Now the percentage of distribution is for week 1 from source 1 to destination1 is 33.33% and for week1 from source 1 to destination 2 to 66.66%
For example the output would be:
week1 source1 destination1 33.33%
week1 source1 destination2 66.66%
week1 source2 destination1 66.66%
week1 source2 destination2 33.33%
week2 source1 destination1 50%
week2 source1 destination2 50%
How can I achieve this result using Java 8 streams.
Say I have list of these objects as List into "records" object:
public class Record {
private String sourceNode;
private String destinationNode;
private String weekIndex;
private String quantity;
}
Map<String, Map<String, List<Record>>> RecordsGroupByWeekAndSource = records.stream()
.collect(Collectors.groupingBy(Record::getWeekIndex, Collectors.groupingBy(Record::getSourceNode)));
This would give me the items group by week and source. But I will have to iterate this map again to calculate the total quantity in each list that resides inside map of map object. But Is there a way I can do this percenatage calcualtion within the groupingBy collection itslef?
Solution 1:[1]
You can create a map with key: week+source and value as total quantity.
CollectingAndThen can be utilize the map and create resulting list:
// Value Objects:
@Data
@AllArgsConstructor
class Records {
String week, source, destination;
int quantity;
}
@Data
@AllArgsConstructor
class Distribution {
String week, source, destination;
float pctDist;
public Distribution(Records r) {
this.week = r.getWeek();
this.source = r.getSource();
this.destination = r.getDestination();
}
}
import static java.util.stream.Collectors.*;
public class SO {
public static void main(String[] args) {
List<Record> recordList = List.of(
new Record("week1", "source1", "destination1", 100),
new Record("week1", "source1", "destination2", 200),
new Record("week1", "source2", "destination1", 200),
new Record("week1", "source2", "destination2", 100),
new Record("week2", "source1", "destination1", 200),
new Record("week2", "source1", "destination2", 200));
Function<Map<String, Integer>, List<Distribution>> distExtractor =
totalQuantityMap -> recordList.stream().map(r -> getDistribution(r,totalQuantityMap)).collect(toList());
List<Distribution> result =
recordList.stream().collect(collectingAndThen(groupingBy(r -> r.getWeek() + r.getSource(),
summingInt(Record::getQuantity)),
distExtractor));
// print the result
result.forEach((rec) -> System.out.println(rec.week + "\t" + rec.source + "\t" + rec.destination + "\t" + rec.pctDist));
}
private static Distribution getDistribution(Record r, Map<String, Integer> weekAndSourceToTotalQuantityMap) {
int total = weekAndSourceToTotalQuantityMap.get(r.getWeek() + r.getSource());
float pctDist = (r.getQuantity() * 100) / total;
var dist = new Distribution(r);
dist.setPctDist(pctDist);
return dist;
}
}
Output:
// Precision can be worked upon in getDistribution method
week1 source1 destination1 33.0
week1 source1 destination2 66.0
week1 source2 destination1 66.0
week1 source2 destination2 33.0
week2 source1 destination1 50.0
week2 source1 destination2 50.0
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 | adarsh |
