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() не принимает правильные аргументы.
Хотя вы были близки, использование вами коллекторов синтаксически неверно.
Во-первых, требуемый метод 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}
@ tigh75 Была ошибка, исправил ответ и предоставил демо, теперь все работает правильно.
Может быть проще получить окончательный результат в два шага: после группировки по вашему 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}}
Когда я пытаюсь это сделать, я получаю сообщение об ошибке
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>, это не сработает.