按多个分组聚合列表中的值并计算 java 中的分布百分比 8

Aggregating values in a list by multiple group by and calculating percentage of distribution in java 8

我有这样的用例,我需要按多个分组依据聚合列表中的值,然后计算每个值的分布百分比并创建一个新列表。

项目列表示例:

week1  source1  destination1   100
week1  source1  destination2   200
week1  source2  destination1   200
week1  source2  destination2   100
week2  source1  destination1   200
week2  source1  destination2   200

据此我想按周和来源分组并计算总量,然后根据数量分配百分比。

例如,来源 1 的第 1 周的总数量为 300,将运往目的地 1(100) 和目的地 2(200)。现在,第 1 周从源 1 到目标 1 的分配百分比为 33.33%,对于第 1 周从源 1 到目标 2 的分配百分比为 66.66%

例如输出将是:

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%

如何使用 Java 8 个流来实现此结果。

假设我将这些对象的列表作为“记录”对象的列表:

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)));

这会给我按周和来源分组的项目。但是我将不得不再次迭代此地图以计算驻留在地图对象地图内的每个列表中的总数。但是有没有一种方法可以在 groupingBy 集合 itslef 中进行百分比计算?

你可以通过两次stream实现:

  1. 在第一个集合中,你可以做 group by 和做 sum
  2. 再次流式传输您的记录并使用第一步的总和结果来计算百分比

示例代码:

import java.util.*;

import java.util.stream.*;

class Record {
    public String week;
    public String source;
    public String destination;
    public Integer qty;

    Record(String week, String source, String destination, Integer qty) {
        this.week = week;
        this.source = source;
        this.destination = destination;
        this.qty = qty;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Record> records = new ArrayList<>();
        records.add(new Record("w1", "hyd", "kur", 10));
        records.add(new Record("w1", "hyd", "gwd", 20));
        records.add(new Record("w2", "hyd", "kur", 40));
        records.add(new Record("w2", "hyd", "gwd", 10));
        
        
        Map<String, Map<String, Integer>> sums = records
        .stream()
        .collect(Collectors.groupingBy(rec -> rec.week,
            Collectors.groupingBy(rec -> rec.source, 
            Collectors.summingInt(rec->rec.qty))));

        records = records
        .stream()
        .map(rec -> {
            rec.qty = rec.qty*100 / sums.get(rec.week).get(rec.source);
            return rec;
        }).collect(Collectors.toList());
        records.forEach((rec)->System.out.println(rec.week+"\t"+rec.source+"\t"+rec.destination+"\t"+rec.qty));
    }
}

您可以创建一个地图,键为:week+source,值为总数量。 CollectingAndThen 可以利用地图并创建结果列表:

// 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;
 }
}

输出:

// 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