使用Collection框架计算价值百分比

时间:2020-04-23 12:50:15

标签: java spring spring-boot collections java-stream

我有 TrainingRequest 列表,其中每个元素都有 Feedback 列表。

@Data
class TrainingRequest{
    @Transient
    List<Feedack> feedback;
}

@Data
class Feedback{
    String Q1;
    String Q2;
}

我需要获取Q1,Q2的所有给定结果,并计算每个值的百分比。

List<TrainingRequest> trainingList = Optional.ofNullable(trainingRequestList).orElseGet(Collections::emptyList)
                                    .stream().map(m -> {
                                        List<Feedback> feedback = findByTrainingRequestId(m.getId());
                                        m.setFeedback(feedback);  // assigning Feedack to TrainingRequest
                                        return m;
                                    }).collect(Collectors.toList());

整理所有反馈信息

List<Feedback> flatMap = trainingList.stream().flatMap(f -> f.getFeedback().stream()).collect(Collectors.toList());

要计算Q1和Q2的每个值,我将其分组并获得计数。我需要获取插入的每个Q1,Q2值的百分比。

Map<String, Map<String, Long>> map = new TreeMap<>();

map.put("Q1", flatMap.stream().collect(Collectors.groupingBy(Feedback::getQ1, Collectors.counting())));
map.put("Q2", flatMap.stream().collect(Collectors.groupingBy(Feedback::getQ2, Collectors.counting())));

当我使用Collectors.counting()时,其输出如下。

{
  "Q1": {
    "unsatisfied": 2,
    "Satisfied": 1,
    "satisfied": 1
  },
  "Q2": {
    "Yes": 4
  }
}

但是我需要它给出我期望的百分比

{
  "Q1": {
    "unsatisfied": 50 %,
    "Satisfied": 25 %,
    "satisfied": 25 %
  },
  "Q2": {
    "Yes": 100 %
  }
}

如何有效地做到这一点?如果需要优化上面的代码,请告诉我。预先感谢

2 个答案:

答案 0 :(得分:3)

您的问题有点不清楚,所以我尝试为自己简化一下逻辑。我想出了一个方法来计算IntStream中的偶数/奇数整数的百分比(这与您要执行的操作并没有什么不同)。

IntStream.range(0, 101).boxed()
         .collect(Collectors.groupingBy(integer -> (integer % 2) == 0 ? "even" : "odd",
             Collectors.collectingAndThen(Collectors.counting(), aLong -> aLong + " %")));

请注意,我们使用collectingAndThen()的值是collect,然后使用mapper/finisher将结果映射到另一个值。

在您的情况下,这将被翻译成这样

map.put("Q1", flatMap.stream().collect(Collectors.groupingBy(Feedback::getQ1,
Collectors.collectingAndThen(Collectors.counting(), count -> (count / flatMap.size()) * 100.00 + " %")));

map.put("Q2", flatMap.stream().collect(Collectors.groupingBy(Feedback::getQ2,
Collectors.collectingAndThen(Collectors.counting(), count -> (count / flatMap.size()) * 100.00 + " %")));

更新

由于您专门询问了优化问题,因此有两点说明

1。当您可以重用现有收藏集时不要创建新收藏集

// this code is unnecessarily creating a new collection
List<TrainingRequest> trainingList = Optional.of(trainingRequestList).orElseGet(Collections::emptyList)
                                    .stream().map(m -> {
                                        List<Feedback> feedback = findByTrainingRequestId(m.getId());
                                        m.setFeedback(feedback);  // assigning Feedack to TrainingRequest
                                        return m;
                                    }).collect(Collectors.toList());

可以简化为此

// to avoid NullPointerExceptions
trainingRequestList = trainingRequestList == null ? Collections.emptyList() : trainingRequestList;
// because java is pass by reference we are able to do this
trainingRequestList.forEach(m -> m.setFeedback(findByTrainingRequestId(m.getId())));

2。如果您要再次流式传输收藏集,请不要收藏

// to hold the count of Q1 an Q2
final Map<String, Integer> count = new HashMap<>();

//  Order(n), n = trainingRequests count
trainingRequestList.forEach(trainingRequest -> {
   List<Feedback> feedbacks = findByTrainingRequestId(trainingRequest.getId());
   //  Order(m), m = feedbacks count
   feedbacks.forEach(f -> {
     count.merge("Q1", f.getQ1(), Integer::sum);
     count.merge("Q2", f.getQ2(), Integer::sum);
   });
   trainingRequest.setFeedback(feedbacks);
}

// finally we can collect the percentage
// Order(1)
int totalCountOfFeedbacks = count.values().stream().mapToInt(Integer::intValue).sum();
Map<String, String> result = count.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> 100.00 * (entry.getValue() / totalCountOfFeedbacks ) + " %"));

请注意,这些优化不会影响您的逻辑当前为Order(n * m)的事实,如果不实际查看代码就很难为您提供进一步的提示。

答案 1 :(得分:2)

这可能不是最佳答案,但是您可以获得结果。 创建一个映射以保留每个Q的总值,然后使用它来计算百分比,

Map<String, Long> totalCountMap = map.entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().values().stream().reduce(Long::sum).orElse(0l)));

Map<String, Map<String, Long>> result = map.entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e1 -> (e1.getValue() * 100 / totalCountMap.get(e.getKey()))))));