在Java 8中,通过多个分组汇总列表中的值,并计算分布百分比。

4

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

以下是一个项目列表示例:

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,其中100个送到目的地1,200个送到目的地2。现在,第1周来自源1到目的地1的分配比例为33.33%,而来自源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的流实现此结果。

假设我有一个名为"records"的对象,它是这些对象的列表:

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 集合本身中进行百分比计算?

最终结果应该是什么?类似于 Map<String, Map<String, Map<String, Double>>> 吗?此外,如果某个源在某周内没有出现,或者某个目的地在给定的周和源中没有出现,那么它们在结果中不出现也可以吗?(即结果中没有0%,除非输入中有0) - Didier L
你可以创建一个代表聚合键的类和一个代表聚合结果的类,而不是使用嵌套的 Maps。 - benebo22
你可以创建一个表示聚合键的类和一个表示聚合结果的类,而不是使用嵌套的映射。这样可以更清晰地表达目标类的意图。 例如,你可以从WeekSourceKey类(包含week和source字段)开始,并使用多个流操作(如reduce)将其转换为(相对)DestinationDistributionByWeekAndSource (包含WeekSourceKey键、destination字符串和distribution浮点数)列表。 是的,在列表中只会出现具有week和source(且不为0%)值的条目。 - benebo22
2个回答

0
你可以通过两次使用流来实现它:
  1. 在第一个集合中,你可以进行分组并求和
  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));
    }
}

0
您可以使用键为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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接