【问题标题】:How to limit in groupBy java stream如何限制groupBy java流
【发布时间】:2018-02-05 11:26:07
【问题描述】:

这是我在课堂上名为Course的课程模型:

public class Course{
    private int courseId;
    private String courseName;
    private Teacher teacher;
}

这是我在课堂上名为Teacher的老师模型:

public class Teacher{
    private int teacherId;
    private String name;
}

我想获得Map<String, List<Course>>,但如果teacherId 重复,只需将Course 添加到地图列表中。

我正在使用groupBy

Map<Integer, List<Course>>  result = courses.stream()
        .collect(Collectors.groupingBy(c -> c.getTeacher().getTeacherId(), Collectors.toList()));

它按预期给出了结果。

但是我想在这里limit,一旦找到5位老师就停止处理并返回结果。

怎么办??

【问题讨论】:

  • 现在怎么办?五位还是十位老师? “停止处理”是什么意思?您对不完整的Course 列表感到满意吗?
  • 可能是一个自定义收集器,其中满足条件后出现的所有元素都只是一个 NOOP
  • 我认为您的问题存在混淆。假设您的列表中有 100 门课程。那么如何确定前五位老师的所有课程都已添加到地图中?一旦将第一门课程添加到第五位老师的地图中,您的要求就会停止。您不需要前五位老师的所有课程吗?
  • @ltiGupta:为什么要麻烦自己使用收集器和分组依据?使用简单的 while 循环遍历列表并将教师添加到 Set 并且您的 while 循环应该在 Set 的大小达到 5 或 10 或 ... 后停止
  • @M.Prokhorov 无论流的来源是什么,您始终可以使用循环,因为您可以从流中获取Iterator

标签: java lambda java-8 java-stream


【解决方案1】:

没有直接支持这一点,因为停止和使用可能不完整的数据是相当不寻常的。

完全收集前五组的直接解决方案是

Set<Integer> firstFive = courses.stream()
    .map(c -> c.getTeacher().getTeacherId())
    .distinct().limit(5)
    .collect(Collectors.toSet());
Map<Integer, List<Course>> result = courses.stream()
    .filter(c -> firstFive.contains(c.getTeacher().getTeacherId()))
    .collect(Collectors.groupingBy(c -> c.getTeacher().getTeacherId()));

到此,前五个教师 ID 的 Course 列表已完成。


遇到第 5 个教师 ID 后真正停止的解决方案,使用循环会更简单:

Map<Integer, List<Course>> result = new HashMap<>();
for(Course c: courses) {
    result.computeIfAbsent(c.getTeacher().getTeacherId(), x -> new ArrayList<>()).add(c);
    if(result.size() == 5) break;
}

但是收集Courses 的列表并没有多大意义,因为事后您不能信任这些列表。请记住,即使源列表的最后一个元素也可能属于第一个遇到的教师 ID,因此即使您只对一位教师的完整课程列表感兴趣,您也需要处理整个列表。

【讨论】:

    【解决方案2】:

    不确定这是否是您要问的。

    Map<Integer, List<Course>> map = new HashMap<>();
    
    courses.stream().filter(course -> map.keySet().size() < 10)
        .forEach(entry -> {
           // The code below can be simplified
          int teacherId = entry.getTeacher().getTeacherId();
          if(map.get(teacherId) != null)
            map.get(teacherId).add(entry);
          else
            map.put(teacherId, Lists.newArrayList(entry));
        });
    

    【讨论】:

    • 您的代码依赖于处理顺序,不能保证。所以它只是碰巧使用当前实现处理顺序流......
    • 不确定我是否理解正确,但是为什么这里的处理顺序很重要。我同意它可能必须遍历更多元素才能获得前 10 个不同的元素。但是,如果您首先找到 10 个不同的,然后再次通过列表进行分组,那将是 2n 复杂性不是吗
    • 这与复杂性无关。您的代码依赖于这样的假设:filter 谓词将在 forEach 添加元素后立即针对下一个元素进行评估,以观察其效果,即地图的新大小。对此没有任何保证。甚至不能保证forEach 会按照源列表的顺序处理元素。您可以使用forEachOrdered修复终端操作,但仍然无法保证中间filter操作的处理顺序。
    • @Holger 我看到这个解决方案不会为老师挑选所有课程(因为它只是检查地图大小)。您能否解释一下为什么即使使用forEachOrdered,它也不适用于生成无序流的源?
    • @user7 forEachOrdered 只保证指定的消费者在遇到顺序中被评估,如果有的话。它不会为传递给filter 的谓词的评估创建任何保证。
    猜你喜欢
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多