Как разбить список объектов на 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
Сам соединяет элементы, используя произвольный разделитель.