Проблема с созданием вложенной карты с использованием потоков и сборщиков

class QuizAnswers {
  List<CheckboxAnswer> checkBoxAnswers;
}

class CheckboxAnswer {
  int questionId;
  // The indices of the selected answer choices
  List<Integer> answer_selections;
}

Вход в мою функцию — это List<QuizAnswers>.

Я хочу создать вывод Map<Integer, Map<Integer, Long>>, который отображает <CheckboxAnswer.questionId : <CheckboxAnswer.answer_selection, total count of answer_selection>. Другими словами, я хочу создать вложенную карту, которая сопоставляет каждый вопрос викторины с множественным выбором с картой, представляющей общее количество вариантов выбора для каждого варианта ответа на этот вопрос викторины.

Предположим, что вход List<QuizAnswers> quizAnswersList как:

[ {questionId: 1, answer_selection: [1,2]},    
  {questionId: 1, answer_selection:[1,2,3,4]},  
  {questionId: 2, answer_selection:[3]},   
  {questionId: 2, answer_selection:[1]} ]

Тогда я хотел бы, чтобы вывод был:

{1 : {1:2, 2:2, 3:1, 4:1}, 2: {1:1, 3:1}}

Потому что вопрос с Id = 1 получил два выбора при выборе ответа 2 и 1 и 1 выбор при выборе ответа 3 и 4, в то время как вопрос с Id=2 имел 1 выбор при выборе ответа 1 и 3.

я пытался

quizAnswersList.stream()
            .flatMap(
                quizAnswers ->
                    quizAnswers.getCheckboxAnswers().stream())
            .collect(
                answer -> {
                  return Collectors.groupingBy(
                      answer.getQuestionId(),
                      answer.getAnswerSelections().stream()
                          .collect(
                              Collectors.groupingBy(
                                  answerSelection -> answerSelection, 
                                  Collectors.counting())));
                        });

Что дает мне ошибку, что первый collect() не принимает правильные аргументы.

Основы программирования на Java
Основы программирования на Java
Java - это высокоуровневый объектно-ориентированный язык программирования, основанный на классах.
Концепции JavaScript, которые вы должны знать как JS программист!
Концепции JavaScript, которые вы должны знать как JS программист!
JavaScript (Js) - это язык программирования, объединяющий HTML и CSS с одной из основных технологий Всемирной паутины. Более 97% веб-сайтов используют...
1
0
52
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Хотя вы были близки, использование вами коллекторов синтаксически неверно.

Во-первых, требуемый метод collect() ожидает коллектор (есть и другой вкусcollect()которые предполагают три «функции»: поставщик, аккумулятор и объединитель; не путай их), вот правильный синтаксис:

.collect(MyCollector);

Теперь о коллекторе groupingBy(). Нам нужна его версия, которая ожидает classifierфункция и нижний коллектор:

.collect(Collectors.groupingBy(function, AnotherCollector);

А в качестве нижестоящего коллектора нам нужно предоставить flatMapping(). Что ожидает функцию, которая должна преобразовывать каждый CheckboxAnswer в поток значения выбора ответа (чтобы иметь возможность сопоставить каждый из них со своим счетом) и нижний коллектор. В свою очередь, в качестве вниз по течению для flatMapping() нам снова нужно указать groupingBy() и counting() как его нижний коллектор.

Окончательная структура коллекционеры будет следующей:

.collect(Collectors.groupingBy(function, // <- getting a question Id
    Collectors.flatMapping(function,     // <- obtaining `answer selection values` as a stream of Integer
        Collectors.groupingBy(function,  // <- Function.identity() is used to retain a `answer selection` without changes
            Collectors.counting()        // <- obtaining a count for each `answer selection`
);

Теперь давайте сложим все вместе.

public static void main(String[] args) {
    List<QuizAnswers> quizAnswersList =
        List.of(new QuizAnswers(List.of(new CheckboxAnswer(1, List.of(1,2)),
                                        new CheckboxAnswer(1, List.of(1,2,3,4)))),
                new QuizAnswers(List.of(new CheckboxAnswer(2, List.of(3)),
                                        new CheckboxAnswer(2, List.of(1)))));

    Map<Integer, Map<Integer, Long>> answerSelectionToCountById = quizAnswersList.stream()
        .map(QuizAnswers::getCheckBoxAnswers)
        .flatMap(List::stream)
        .collect(Collectors.groupingBy(
            CheckboxAnswer::getQuestionId,
                Collectors.flatMapping(checkboxAnswer -> checkboxAnswer.getAnswerSelections().stream(),
                    Collectors.groupingBy(Function.identity(),
                        Collectors.counting()))));
    
    answerSelectionToCountById.forEach((k, v) -> System.out.println(k + " : " + v));
}

Выход

1 : {1=2, 2=2, 3=1, 4=1}
2 : {1=1, 3=1}

Когда я пытаюсь это сделать, я получаю сообщение об ошибке incompatible types: inference variable K has incompatible bounds equality constraints: java.lang.Integer lower bounds: java.util.List<java.lang.Integer> [compiler.err.prob.found.req] в первой функции сбора. Я считаю, что этот код будет работать, если CheckboxAnswer.getAnswerSelections() вернет целое число, но поскольку он возвращает List<Int>, это не сработает.

tigh75 16.05.2022 23:10

@ tigh75 Была ошибка, исправил ответ и предоставил демо, теперь все работает правильно.

Alexander Ivanchenko 16.05.2022 23:23

Может быть проще получить окончательный результат в два шага: после группировки по вашему questionId вам нужно сопоставить с вашим answer_selections. Это можно сделать с помощью Collectors.mapping, чтобы получить промежуточный результат Map<Integer,List<List<Integer>>>, который может выглядеть примерно так:

Map<Integer, List<List<Integer>>> intermediate =
        quizAnswersList.stream()
                .flatMap(quizAnswers -> quizAnswers.getCheckBoxAnswers().stream())
                .collect(Collectors.groupingBy(CheckboxAnswer::getQuestionId,
                                Collectors.mapping(CheckboxAnswer::getAnswer_selections, Collectors.toList())));

System.out.println(intermediate);

Это даст вам вывод, например:

{1=[[1, 2], [1, 2, 3, 4]], 2=[[3], [1]]}

Поскольку это не то, что вы действительно хотели, вам нужно сделать еще один шаг и обернуть сопоставление, которое сделано здесь.

Collectors.mapping(CheckboxAnswer::getAnswer_selections, Collectors.toList())

в Collectors.collectingAndThen, чтобы преобразовать значения приведенной выше карты, которые имеют тип списка списков, в Map<Integer,Long>, что можно сделать, как показано ниже (включая описанный выше шаг, который был полезен только для объяснения промежуточного результата, нужен только приведенный ниже код) :

Map<Integer, Map<Integer, Long>> finalresult =
quizAnswersList.stream()
        .flatMap(quizAnswers -> quizAnswers.getCheckBoxAnswers().stream())
        .collect(Collectors.groupingBy(CheckboxAnswer::getQuestionId,
                Collectors.collectingAndThen(
                        Collectors.mapping(CheckboxAnswer::getAnswer_selections, Collectors.toList()),
                        lists -> lists.stream()
                                .flatMap(List::stream)
                                .collect(Collectors.groupingBy(Function.identity(),Collectors.counting())))));

System.out.println(finalresult);

который даст вам желаемый результат

{1={1=2, 2=2, 3=1, 4=1}, 2={1=1, 3=1}}

Другие вопросы по теме