Разделить список на подсписки фиксированного размера

8. Разделить список на подсписки фиксированного размера

Условие задачи:
Дан список элементов и размер группы size.

Необходимо разделить исходный список на последовательные подсписки размером не более size.

Если количество элементов не делится на size без остатка, последний подсписок должен содержать оставшиеся элементы.

Например:

Список: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
size = 3

Результат:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

Спойлеры к решению

Подсказки
💡 Проходи по списку с шагом size.
💡 Начало очередного подсписка — текущий индекс i.
💡 Конец можно вычислить как Math.min(i + size, list.size()).
💡 Необходимо проверить, что size > 0.
💡 subList() возвращает представление части исходного списка, а не независимую копию.

Решение

Простой вариант через цикл:

public static <T> List<List<T>> partition(List<T> list, int size) {
    if (size <= 0) {
        throw new IllegalArgumentException("size must be greater than 0");
    }

    List<List<T>> result = new ArrayList<>();

    for (int i = 0; i < list.size(); i += size) {
        int toIndex = Math.min(i + size, list.size());

        result.add(new ArrayList<>(list.subList(i, toIndex)));
    }

    return result;
}

Например:

List<Integer> numbers = List.of(
        1, 2, 3, 4, 5,
        6, 7, 8, 9, 10
);

System.out.println(partition(numbers, 3));

Результат:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

Цикл двигается по исходному списку с шагом:

i += size

Правая граница очередной группы вычисляется так:

Math.min(i + size, list.size())

Поэтому для последней неполной группы индекс никогда не выйдет за границы списка.

new ArrayList<>(...) используется намеренно:

new ArrayList<>(list.subList(i, toIndex))

subList() сам по себе возвращает представление исходного списка. Если нужны независимые подсписки, их лучше скопировать.

Вариант через Stream API:

public static <T> List<List<T>> partitionStream(List<T> list, int size) {
    if (size <= 0) {
        throw new IllegalArgumentException("size must be greater than 0");
    }

    int groups = (list.size() + size - 1) / size;

    return IntStream.range(0, groups)
            .mapToObj(i -> {
                int fromIndex = i * size;
                int toIndex = Math.min(fromIndex + size, list.size());

                return new ArrayList<>(
                        list.subList(fromIndex, toIndex)
                );
            })
            .collect(Collectors.toList());
}

Для обычного списка с быстрым доступом по индексам временная сложность — O(n), дополнительная память — O(n) для результирующих подсписков.