简体   繁体   English

在Java 8中限制groupBy

[英]Limit groupBy in Java 8

How can I limit groupBy by each entry? 如何通过每个条目限制groupBy?

For example (based on this example: stream 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"));

This method return simple group: 此方法返回简单组:

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

What if I want to limit the returned collections? 如果我想限制返回的集合怎么办? Let's assume I want only the first N classes for every teacher. 让我们假设我只想要每个老师的前N个课程。 How can it be done? 怎么做到呢?

It would be possible to introduce a new collector that limits the number of elements in the resulting list. 可以引入一个新的收集器来限制结果列表中的元素数量。

This collector will retain the head elements of the list ( in encounter order ). 此收集器将保留列表的头元素( 按遭遇顺序 )。 The accumulator and combiner throw away every elements when the limit has been reached during collection. 在收集期间达到限制时,累加器和组合器会丢弃每个元素。 The combiner code is a little tricky but this has the advantage that no additional elements are added only to be thrown away later. 组合器代码有点棘手,但这样做的好处是不会添加额外的元素,只是为了以后丢弃。

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;
                }
           );
}

And then use it like this: 然后像这样使用它:

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

You could use collectingAndThen to define a finisher operation on the resulting list. 你可以使用collectingAndThen所得到的名单上定义的整理操作。 This way you can limit, filter, sort, ... the lists: 这样你可以限制,过滤,排序......列表:

int limit = 2;

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

For this you need to .stream() the result of your Map. 为此,您需要.stream() Map的结果。 You can do this by doing: 你可以这样做:

// 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()
        ));

This isn't a very optimal way to do it. 这不是一种非常理想的方法。 But if you .limit() on the initial list, then the grouping results would be incorrect. 但是如果你在初始列表中使用.limit() ,则分组结果将是不正确的。 This is the safest way to guarantee the limit. 这是保证限制的最安全的方法。

EDIT: 编辑:

As stated in the comments this limits the teacher, not the class per teacher. 正如评论中所述,这限制了教师,而不是每位教师的课程。 In that case you can do: 在这种情况下,你可以这样做:

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

This would give you the desired result, but it still categorizes all the elements of the stream: 这将为您提供所需的结果,但它仍然会对流的所有元素进行分类:

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)))));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM