【问题标题】:Limit groupBy in Java 8Java 8 中的限制 groupBy
【发布时间】:2023-04-01 06:09:01
【问题描述】:

如何按每个条目限制 groupBy?

例如(基于此示例:stream groupBy):

studentClasses.add(new StudentClass("Kumar", 101, "Intro to Web"));
studentClasses.add(new StudentClass("White", 102, "Advanced Java"));
studentClasses.add(new StudentClass("Kumar", 101, "Intro to Cobol"));
studentClasses.add(new StudentClass("White", 101, "Intro to Web"));
studentClasses.add(new StudentClass("White", 102, "Advanced Web"));
studentClasses.add(new StudentClass("Sargent", 106, "Advanced Web"));
studentClasses.add(new StudentClass("Sargent", 103, "Advanced Web"));
studentClasses.add(new StudentClass("Sargent", 104, "Advanced Web"));
studentClasses.add(new StudentClass("Sargent", 105, "Advanced Web"));

此方法返回简单组:

   Map<String, List<StudentClass>> groupByTeachers = studentClasses
            .stream().collect(
                    Collectors.groupingBy(StudentClass::getTeacher));

如果我想限制返回的集合怎么办? 假设我只想要每个老师的前 N ​​节课。怎么办?

【问题讨论】:

  • 第一个是什么意思?,你的意思是类号最小的类,ASCIIBetically 最小的名称,还是随机选择的 N 个类。注意:类集可能是无序的。
  • @PeterLawrey 你是对的,我没有提到这一点,对我来说顺序无关紧要,但如果我们想要一个更彻底和更通用的解决方案 - 如果你添加一个排序示例(按一个字段)

标签: java java-8 grouping java-stream collectors


【解决方案1】:

可以引入一个新的收集器来限制结果列表中的元素数量。

此收集器将保留列表的头部元素 (in encounter order)。当收集过程中达到限制时,累加器和组合器会丢弃所有元素。组合器代码有点棘手,但这样做的好处是没有添加额外的元素,只会在以后被丢弃。

private static <T> Collector<T, ?, List<T>> limitingList(int limit) {
    return Collector.of(
                ArrayList::new, 
                (l, e) -> { if (l.size() < limit) l.add(e); }, 
                (l1, l2) -> {
                    l1.addAll(l2.subList(0, Math.min(l2.size(), Math.max(0, limit - l1.size()))));
                    return l1;
                }
           );
}

然后像这样使用它:

Map<String, List<StudentClass>> groupByTeachers = 
       studentClasses.stream()
                     .collect(groupingBy(
                          StudentClass::getTeacher,
                          limitingList(2)
                     ));

【讨论】:

    【解决方案2】:

    您可以使用collectingAndThen 在结果列表上定义修整器操作。通过这种方式,您可以限制、过滤、排序……列表:

    int limit = 2;
    
    Map<String, List<StudentClass>> groupByTeachers =
        studentClasses.stream()
                      .collect(
                           groupingBy(
                               StudentClass::getTeacher,
                               collectingAndThen(
                                   toList(),
                                   l -> l.stream().limit(limit).collect(toList()))));
    

    【讨论】:

    • 这仍然会在值已添加到地图后对其进行过滤,但这是迄今为止最好的答案。
    • 终结者的想法很好,但终结者不需要 O(n) 成本。你可以改用list -&gt; list.size() &lt;= limit ? list : list.subList(0, limit)) 之类的方法。但我仍然更喜欢 Tunaki 的解决方案,它根本不需要在列表中添加额外的元素。
    【解决方案3】:

    为此,您需要.stream() 您的地图结果。你可以这样做:

    // Part that comes from your example
    Map<String, List<StudentClass>> groupByTeachers = studentClasses
                .stream().collect(
                        Collectors.groupingBy(StudentClass::getTeacher));
    
    // Create a new stream and limit the result
    groupByTeachers =
        groupByTeachers.entrySet().stream()
            .limit(N) // The actual limit
            .collect(Collectors.toMap(
                e -> e.getKey(),
                e -> e.getValue()
            ));
    

    这不是一个非常理想的方法。但是如果你在初始列表中.limit(),那么分组结果将是不正确的。这是保证限额的最安全方式。

    编辑:

    正如 cmets 中所述,这限制了教师,而不是每位教师的班级。在这种情况下,您可以这样做:

    groupByTeachers =
            groupByTeachers.entrySet().stream()
                .collect(Collectors.toMap(
                    e -> e.getKey(),
                    e -> e.getValue().stream().limit(N).collect(Collectors.toList()) // Limit the classes PER teacher
                ));
    

    【讨论】:

    • 不是很理想,我认为他的意思是在初始分组中这样做。
    • 这限制了返回的教师人数,而不是每位教师的班级数。
    • 在后处理步骤中使用Map.replaceAll 比为每个元素单独创建流更好。但无论如何@Tunaki 的回答更好。
    【解决方案4】:

    这会给你想要的结果,但它仍然对流的所有元素进行分类:

    final int N = 10;
    final HashMap<String, List<StudentClass>> groupByTeachers = 
            studentClasses.stream().collect(
                groupingBy(StudentClass::getTeacher, HashMap::new,
                    collectingAndThen(toList(), list -> list.subList(0, Math.min(list.size(), N)))));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多