Search code examples
javajava-8hashmapjava-streamcollectors

Creating a nested Map using Streams and Collectors


class QuizAnswers {
  List<MultipleChoiceAnswer> multipleChoiceAnswers;
  List<FreeResponseAnswer> freeResponseAnswers; //not relevant to this question
}

class MultipleChoiceAnswer {
  int questionId;
  // The index of the selected multiple choice question
  int answer_selection;
}

The input to my function is a List<QuizAnswers>.

I want to create an output of Map<Integer, Map<Integer, Long>> that maps <MultipleChoiceAnswer.questionId : <MultipleChoiceAnswer.answer_selection, total count of answer_selection>. In other words, I want to create a nested map that maps each multiple choice 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: 2},    
  {questionId: 1, answer_selection:2},  
  {questionId: 1, answer_selection:3},   
  {questionId: 2, answer_selection:1} ]

Then I would want the output to be:

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

Because the question with Id = 1 received two selections on answer choice 2 and 1 selection on answer choice 3 while the question with Id=2 had 1 selection on answer choice 1.

I have tried

quizAnswersList.stream()
            .map(
                quizAnswers ->
                    quizAnswers.getMultipleChoiceAnswers().stream()
                        .collect(
                            Collectors.groupingBy(
                                MultipleChoiceAnswer::getQuestionId,
                                Collectors.groupingBy(
                                    MultipleChoiceAnswer::getAnswerSelection,
                                    Collectors.counting()))));

Which is giving me an error. I am not very familiar with streams and collectors in general, so I'd love to learn how to do this correctly.


Solution

  • I want to create an output of Map<Integer, Map<Integer, Long>> that maps <MultipleChoiceAnswer.questionId : <MultipleChoiceAnswer.answer_selection>, total count of answer_selection>.

    You were close. You just didn't flatMap the MultipleChoiceAnswers onto the stream so you had a nested stream and that was causing the problem.

    Based on your edited question, here is what I came up with.

    List<MultipleChoiceAnswer> mca =
            List.of(new MultipleChoiceAnswer(1, 2),
                    new MultipleChoiceAnswer(1, 2),
                    new MultipleChoiceAnswer(1, 3),
                    new MultipleChoiceAnswer(2, 1));
    
    // more could be added to the List.  You only provided one.
    List<QuizAnswers> list = List.of(new QuizAnswers(mca));
    
    • flatMap all the MultipleChoice lists
    • group them by the questionId
    • then subgroup them according to AnswerSelection and get a count
    • then you get the Map output you requested.
    Map<Integer,Map<Integer,Long>> map = list.stream()
            .flatMap(s -> s.getMultipleChoiceAnswers().stream())
            .collect(Collectors.groupingBy(
                    MultipleChoiceAnswer::getQuestionId,
                    Collectors.groupingBy(
                            MultipleChoiceAnswer::getAnswerSelection,
                            Collectors.counting())));
    
    map.entrySet().forEach(System.out::println);
    
    

    prints

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

    Questions

    • how do you want to handle multiple QuizAnswer instances?
    • how do you want to handle multiple MulitpleChoiceAnswer lists. You only provided one of each.

    They could all be flatmapped together and processed as above. But I think there could be some differences in the Answers (perhaps for different tests) which you don't want grouped and counted as the same.

    Example

    If I add the following to the List<QuizAnswers>

    List<MultipleChoiceAnswer> mca2 =
    List.of(new MultipleChoiceAnswer(1, 2),
            new MultipleChoiceAnswer(1, 2),
            new MultipleChoiceAnswer(5, 2),
            new MultipleChoiceAnswer(5, 2));
    

    And process using the above solution, the output would be

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