Как разбить список объектов на Java 8?

Учитывая java.util.List с n элементы и желаемый размер страницы mЯ хочу преобразовать его в карту, содержащую n/m+n%m элементы. Каждый элемент карты должен содержать m элементы.

Вот пример с целыми числами:

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

    // What is the equivalent Java 8 code to create the map below from my list?

    Map<Integer, List<Integer>> map = new HashMap<>();
    map.put(0, Arrays.asList(1,2,3));
    map.put(1, Arrays.asList(4,5,6));
    map.put(2, Arrays.asList(7,8,9));
    map.put(3, Arrays.asList(10));

Возможно ли это с помощью Java 8?

3 ответа

Решение

Вы могли бы использовать IntStream.iterate в сочетании с toMap коллекционер и тому subList метод на List (спасибо Duncan Jones за упрощения).

import static java.util.stream.Collectors.toMap;
import static java.lang.Math.min;

...

static Map<Integer, List<Integer>> partition(List<Integer> list, int pageSize) {
    return IntStream.iterate(0, i -> i + pageSize)
          .limit((list.size() + pageSize - 1) / pageSize)
          .boxed()
          .collect(toMap(i -> i / pageSize,
                         i -> list.subList(i, min(i + pageSize, list.size()))));
}

Сначала вы рассчитываете количество ключей, которые вам нужны на карте. Это дано (list.size() + pageSize - 1) / pageSize (это будет пределом потока).

Затем вы создаете поток, который создает последовательность 0, pageSize, 2* pageSize, ...,

Теперь для каждого значения i вы берете соответствующий subList что будет нашим значением (вам нужна дополнительная проверка для последнего subList для того, чтобы не выйти за пределы), для которого вы сопоставляете соответствующий ключ, который будет последовательность 0/pageSize, pageSize/pageSize, 2*pageSize/pageSize что вы делите на pageSize чтобы получить естественную последовательность 0, 1, 2, ...,

Трубопровод может быть безопасно запущен параллельно (вам может понадобиться toConcurrentMap коллектор вместо). Как прокомментировал Брайан Гетц (спасибо, что напомнили мне об этом), iterate не стоит, если вы хотите распараллелить поток, так вот версия с range,

return IntStream.range(0, (list.size() + pageSize - 1) / pageSize)
                .boxed()
                .collect(toMap(i -> i ,
                               i -> list.subList(i * pageSize, min(pageSize * (i + 1), list.size()))));

Как и в вашем примере (список из 10 элементов с размером страницы 3), вы получите следующую последовательность:

0, 3, 6, 9, 12, 15, ... что вы ограничиваете (10 + 3 - 1) / 3 = 12 / 3 = 4, которые позволяют последовательность 0, 3, 6, 9, Теперь каждое значение сопоставляется с соответствующим подсписком:

0 / pageSize = 0 -> list.subList(0, min(0 + pageSize, 10)) = list.subList(0, 3);
3 / pageSize = 1 -> list.subList(3, min(3 + pageSize, 10)) = list.subList(3, 6);
6 / pageSize = 2 -> list.subList(6, min(6 + pageSize, 10)) = list.subList(6, 9);
9 / pageSize = 3 -> list.subList(9, min(9 + pageSize, 10))  = list.subList(6, 10);
                                      ^
                                      |
                        this is the edge-case for the last sublist to
                        not be out of bounds


Если вы действительно хотите Map<Integer, String> Вы можете заменить функцию отображения значений на

import static java.util.stream.Collectors.joining;

...

i -> list.subList(i, min(i + pageSize, list.size()))
         .stream()
         .map(Object::toString)
         .collect(joining(","))

которые просто собирают элементы, разделенные запятой, в одну строку.

Простое решение с использованием Guava: com.google.common.collect.Lists # раздел:

    List<List<Integer>> partition = Lists.partition(list, 3); //<- here
    Map map = IntStream.range(0, partition.size()).boxed().collect(Collectors.toMap(
                    Function.identity(),
                    i -> partition.get(i)));

Как отмечено в комментариях, это также работает, если список не является естественной последовательностью целых чисел. Вы должны будете использовать сгенерированный IntStream затем и обратитесь к элементам в списке по индексу.

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

Map<Integer, String> map = IntStream
    .range(0, list.size())
    .boxed()
    .collect(groupingBy(
        i -> i / 3, //no longer i-1 because we start with 0
        mapping(i -> list.get((int) i).toString(), joining(","))
        ));

//result: {0="1,2,3", 1="4,5,6", 2="7,8,9", 3="10"}

Начнем с IntStream представляющие индексы списка.

groupingBy группирует элементы по некоторому классификатору. В вашем случае он группирует х элементов на странице.

mapping применяет функцию отображения к элементам и собирает их впоследствии. Отображение необходимо, потому что joiningтолько принимает CharSequence, joining Сам соединяет элементы, используя произвольный разделитель.

Другие вопросы по тегам