【问题标题】:Calculate the percentage of value using Collection framework使用 Collection 框架计算价值的百分比
【发布时间】:2020-08-06 19:18:30
【问题描述】:

我有 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 %
  }
}

如何以有效的方式做到这一点?上面的代码需要优化吗?

【问题讨论】:

  • 我对你发布的代码 sn-ps 有点困惑,你有一个完整的例子,我可以直接转储到 IDE 中运行吗?
  • 我正在从数据库中获取数据,这就是我实现和正常工作的方式。我认为你可以这样做

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


【解决方案1】:

这可能不是一个优化的答案,但您可以获得结果。 创建一个地图来保存每个 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()))))));

【讨论】:

  • 谢谢,我已经尝试过与您的代码相关的东西,但问题是如何优化它。但是这段代码运行良好。
【解决方案2】:

你的问题有点不清楚,所以我试着为自己简化一下逻辑。我想出了一个片段来计算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)这一事实,如果不实际查看代码,将很难为您提供进一步的提示。

【讨论】:

  • 非常感谢,这是一件好事。我想知道这真的优化了!!
猜你喜欢
  • 2013-11-15
  • 1970-01-01
  • 2015-10-11
  • 2020-06-25
  • 2013-09-07
  • 2012-09-16
  • 2018-04-19
  • 1970-01-01
相关资源
最近更新 更多