class QuizAnswers {
List<CheckboxAnswer> checkBoxAnswers;
}
class CheckboxAnswer {
int questionId;
// The indices of the selected answer choices
List<Integer> answer_selections;
}
The input to my function is a List<QuizAnswers>
.
I want to create an output of Map<Integer, Map<Integer, Long>>
that maps <CheckboxAnswer.questionId : <CheckboxAnswer.answer_selection, total count of answer_selection>
. In other words, I want to create a nested map that maps each multiple selection quiz question to a map representing the total number of selections on each answer choice of that quiz question.
Suppose the input List<QuizAnswers> quizAnswersList
as:
[ {questionId: 1, answer_selection: [1,2]},
{questionId: 1, answer_selection:[1,2,3,4]},
{questionId: 2, answer_selection:[3]},
{questionId: 2, answer_selection:[1]} ]
Then I would want the output to be:
{1 : {1:2, 2:2, 3:1, 4:1}, 2: {1:1, 3:1}}
Because the question with Id = 1
received two selections on answer choice 2
and 1
and 1
selection on answer choice 3
and 4
while the question with Id=2
had 1
selection on answer choice 1
and 3
.
I have tried
quizAnswersList.stream()
.flatMap(
quizAnswers ->
quizAnswers.getCheckboxAnswers().stream())
.collect(
answer -> {
return Collectors.groupingBy(
answer.getQuestionId(),
answer.getAnswerSelections().stream()
.collect(
Collectors.groupingBy(
answerSelection -> answerSelection,
Collectors.counting())));
});
Which is giving me an error that the first collect() is not taking the right arguments.
CodePudding user response:
Although you were close, your usage of collectors is syntactically incorrect.
Firstly, the required method collect()
expects a collector (there's also another flavor of collect()
that expect tree "functions": supplier, accumulator and combiner; don't confuse them), that's the correct syntax:
.collect(MyCollector);
Now, regarding the groupingBy()
collector. We need its version that expects a classifier
function and a downstream collector:
.collect(Collectors.groupingBy(function, AnotherCollector);
And as a downstream collector we need to provide flatMapping()
. Which expects a function that should transform each CheckboxAnswer
into a stream of answer selection values (in order to be able to map each of them to its count), and a downstream collector. In turn as the downstream of flatMapping()
again we need to provide groupingBy()
and counting()
as its downstream collector.
The final structure of collectors will be the following:
.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`
);
Now, let's put all the things together.
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));
}
Output
1 : {1=2, 2=2, 3=1, 4=1}
2 : {1=1, 3=1}
CodePudding user response:
It is may be easier to get to the final result in two steps: After grouping by your questionId
you need to map to your answer_selections
. This can be done using Collectors.mapping
so that you end up with an intermediate result of Map<Integer,List<List<Integer>>>
which could look like something like:
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);
This will give you an output like:
{1=[[1, 2], [1, 2, 3, 4]], 2=[[3], [1]]}
Since the above is not what you realy wanted you need to do one more step and wrap the mapping which is done here
Collectors.mapping(CheckboxAnswer::getAnswer_selections, Collectors.toList())
in to Collectors.collectingAndThen
to turn the values of above map which are of type list of lists to a Map<Integer,Long>
which can be done like showed below (including the above step, which only was useful to explain the intermediate result, only the code below is needed):
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);
which will give you the desired result
{1={1=2, 2=2, 3=1, 4=1}, 2={1=1, 3=1}}