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
的问题在回答选择2
和1
上接收到两个选择,并且在回答选择3
和4
上接收到1
选择,而具有Id=2
的问题在答案选择1
和3
上接收到了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}}