使用流和收集器创建嵌套映射时出现问题


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

我的函数的输入是一个List<QuizAnswers>

我想要创建映射<CheckboxAnswer.questionId : <CheckboxAnswer.answer_selection, total count of answer_selection>Map<Integer, Map<Integer, Long>>的输出。换句话说,我想创建一个嵌套映射,将每个多选测验问题映射到一个映射,该映射表示该测验问题的每个答案选项的选择总数。

假设输入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的问题在回答选择21上接收到两个选择,并且在回答选择34上接收到1选择,而具有Id=2的问题在答案选择13上接收到了1选择。

我试过

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}

通过两个步骤可以更容易地获得最终结果:按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}}

最新更新