Java streams average



我需要使用流创建两个方法。返回每个任务的平均分数的方法。

public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {}

和一个返回平均分最高的任务的方法。

public String easiestTask(Stream<CourseResult> results) {}

我只能修改这两个方法。

这是CourseResult类

public class CourseResult {
private final Person person;
private final Map<String, Integer> taskResults;
public CourseResult(final Person person, final Map<String, Integer> taskResults) {
this.person = person;
this.taskResults = taskResults;
}
public Person getPerson() {
return person;
}
public Map<String, Integer> getTaskResults() {
return taskResults;
}
}

和创建CourseResult对象的方法。

private final String[] programTasks = {"Lab 1. Figures", "Lab 2. War and Peace", "Lab 3. File Tree"};
private final String[] practicalHistoryTasks = {"Shieldwalling", "Phalanxing", "Wedging", "Tercioing"};
private Stream<CourseResult> programmingResults(final Random random) {
int n = random.nextInt(names.length);
int l = random.nextInt(lastNames.length);
return IntStream.iterate(0, i -> i + 1)
.limit(3)
.mapToObj(i -> new Person(
names[(n + i) % names.length],
lastNames[(l + i) % lastNames.length],
18 + random.nextInt(20)))
.map(p -> new CourseResult(p, Arrays.stream(programTasks).collect(toMap(
task -> task,
task -> random.nextInt(51) + 50))));
}
private Stream<CourseResult> historyResults(final Random random) {
int n = random.nextInt(names.length);
int l = random.nextInt(lastNames.length);
AtomicInteger t = new AtomicInteger(practicalHistoryTasks.length);
return IntStream.iterate(0, i -> i + 1)
.limit(3)
.mapToObj(i -> new Person(
names[(n + i) % names.length],
lastNames[(l + i) % lastNames.length],
18 + random.nextInt(20)))
.map(p -> new CourseResult(p,
IntStream.iterate(t.getAndIncrement(), i -> t.getAndIncrement())
.map(i -> i % practicalHistoryTasks.length)
.mapToObj(i -> practicalHistoryTasks[i])
.limit(3)
.collect(toMap(
task -> task,
task -> random.nextInt(51) + 50))));
}

基于这些方法,我可以通过将该任务的分数总和除以3来计算每个任务的平均值,因为只有3个人,我可以使它除以一个等于流中CourseResult对象数量的数字,如果这些方法得到他们的.limit(3)改变。我不知道如何访问taskResults Map的键。我想我需要它们返回一个唯一键的映射。每个唯一键的值应该是分配给这些键的taskResults map中的值的平均值。

对于您的第一个问题:将每个CourseResult映射到taskResults,平面映射以从所有CourseResult中获取每个taskResults映射的所有条目,按映射键(任务名称)分组并收集相同键的平均值:

public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {
return results.map(CourseResult::getTaskResults)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.averagingInt(Map.Entry::getValue)));
}

对于第二个问题,您可以使用相同的方法来计算每个任务的平均值,并最终对结果映射的条目进行流处理,以找到具有最高平均值的任务。

public String easiestTask(Stream<CourseResult> results) {
return results.map(CourseResult::getTaskResults)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.averagingInt(Map.Entry::getValue)))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("No easy task found");
}

为避免代码重复,可以在第二个方法中调用第一个方法:

public String easiestTask(Stream<CourseResult> results) {
return averageScoresPerTask(results).entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("No easy task found");
}

编辑

要自定义计算平均值,无论你的地图包含多少项目,不要使用内置的操作,如Collectors.averagingIntCollectors.averagingDouble。相反,将收集器包装在collectingAndThen中,并使用Collectors.summingInt对分数求和,最后在收集后根据任务名称是否以Lab开头使用除数进行除法:

public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {
return results.map(CourseResult::getTaskResults)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Map.Entry::getKey, Collectors.summingInt(Map.Entry::getValue)),
map -> map.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getKey().startsWith("Lab") ? e.getValue() / 3. : e.getValue() / 4.))
));
}

要创建包含每个任务平均分数映射,您需要平铺流中每个CourseResult结果对象的映射taskResults,并按(即按任务名称)对数据进行分组。

为此,您可以使用收集器groupingBy(),因为它的下游收集器负责从映射到相同任务的得分值中计算平均值您可以使用averagingDouble()

这就是它的样子:

public Map<String, Double> averageScoresPerTask(Stream<CourseResult> results) {

return results
.map(CourseResult::getTaskResults)       // Stream<Map<String, Integer>> - stream of maps
.flatMap(map -> map.entrySet().stream()) // Stream<Map.Entry<String, Integer>> - stream of entries
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.averagingDouble(Map.Entry::getValue)
));
}

要找到最简单的任务,您可以使用此映射而不是将流作为参数传递,因为此方法的逻辑需要应用相同的操作。在现实生活场景中,当您检索存储在某处的数据时(最好避免对其进行双重处理),并且在您的情况下,您不能从源生成两次流并将其传递到这两个方法中,因为在您的情况下,流数据是随机的。将相同的流传递给两个方法不是一个选项,因为您只能执行一次流管道,当它到达终端操作时-它完成了,你不能再使用它了,因此你不能在这两个方法中传递带有随机数据的相同流。

public String easiestTask(Map<String, Double>  averageByTask) {

return averageByTask.entrySet().stream()
.max(Map.Entry.comparingByValue()) // produces result of type Optianal<Map.Entry<String, Double>>
.map(Map.Entry::getKey)            // transforming into Optianal<String>
.orElse("no data");                // or orElseThrow() if data is always expected to be present depending on your needs
}

最新更新