Рассчитать процент стоимости с использованием структуры коллекции - PullRequest
7 голосов
/ 23 апреля 2020

У меня есть список TrainingRequest , где каждый элемент имеет список Обратная связь .

@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 ]

4 голосов
/ 23 апреля 2020

Это может быть не оптимизированный ответ, но вы можете получить результат. Создайте карту, чтобы сохранить общие значения для каждого 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()))))));
3 голосов
/ 23 апреля 2020

Ваш вопрос был немного неясен, поэтому я попытался немного упростить логику c для себя. Я придумал фрагмент кода, чтобы вычислить процент четных / нечетных целых чисел в 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 ) + " %"));

Обратите внимание, что эти оптимизации не повлияют на тот факт, что ваш логин c в настоящее время Order(n * m), вам будет трудно предоставить дальнейшие подсказки, фактически не смотря на код.

...