Ограничить группуBy в Java 8

Как я могу ограничить 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 классов для каждого учителя. Как это можно сделать?

Ответы

Ответ 1

Можно было бы ввести новый коллекционер, который ограничивает количество элементов в результирующем списке.

Этот коллекционер сохранит элементы заголовка списка (в порядке встречи). Аккумулятор и сумматор выбрасывают все элементы, когда предел достигнут во время сбора. Комбинированный код немного сложнее, но это имеет то преимущество, что дополнительные элементы не добавляются только для того, чтобы быть выброшенными позже.

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

Для этого вам нужно .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() в исходном списке, то результаты группировки будут неверными. Это самый безопасный способ гарантировать лимит.

EDIT:

Как указано в комментариях, это ограничивает учитель, а не класс на учителя. В этом случае вы можете:

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

Ответ 3

Вы можете использовать collectingAndThen, чтобы определить операцию финишера в результирующем списке. Таким образом вы можете ограничить, фильтровать, сортировать,... списки:

int limit = 2;

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

Ответ 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)))));