19

如何按每个条目限制 groupBy?

例如(基于此示例:流 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 ​​节课。怎么做到呢?

4

4 回答 4

22

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

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

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)
                     ));
于 2015-11-22T10:43:37.200 回答
7

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

int limit = 2;

Map<String, List<StudentClass>> groupByTeachers =
    studentClasses.stream()
                  .collect(
                       groupingBy(
                           StudentClass::getTeacher,
                           collectingAndThen(
                               toList(),
                               l -> l.stream().limit(limit).collect(toList()))));
于 2015-11-22T10:50:42.373 回答
4

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

// 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()在初始列表中,那么分组结果就会不正确。这是保证限额的最安全方式。

编辑:

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

groupByTeachers =
        groupByTeachers.entrySet().stream()
            .collect(Collectors.toMap(
                e -> e.getKey(),
                e -> e.getValue().stream().limit(N).collect(Collectors.toList()) // Limit the classes PER teacher
            ));
于 2015-11-22T10:19:38.397 回答
3

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

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)))));
于 2015-11-22T10:39:02.583 回答