'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