Given a java.util.List
with n
elements and a desired page size m
, I want to transform it to a map containing n/m+n%m
elements. Each map element shall contain m
elements.
Here's an example with integers:
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));
Is this possible, using Java 8?
You could use IntStream.iterate
combined with the toMap
collector and the subList
method on List
(thanks to Duncan for the simplifications).
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()))));
}
You first calculate the numbers of keys you need in the map. This is given by (list.size() + pageSize - 1) / pageSize
(this will be the limit of the stream).
Then you create a Stream that creates the sequence 0, pageSize, 2* pageSize, ...
.
Now for each value i
you grab the corresponding subList
which will be our value (you need an additional check for the last subList
for not getting out of bounds) for which you map the corresponding key which will be the sequence 0/pageSize, pageSize/pageSize, 2*pageSize/pageSize
that you divide by pageSize
to get the natural sequence 0, 1, 2, ...
.
The pipeline can be safely run in parallel (you may need to use the toConcurrentMap
collector instead). As Brian Goetz commented (thanks for reminding me that), iterate
is not worth if you want to parallelize the stream, so here's a version with 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()))));
So as with your example (a list of 10 elements with a page size of 3), you'll get the following sequence:
0, 3, 6, 9, 12, 15, ...
that you limit to (10 + 3 - 1) / 3 = 12 / 3 = 4
, which let the sequence 0, 3, 6, 9
. Now each value is mapped to its corresponding sublist:
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>
you could replace the value mapper function with
import static java.util.stream.Collectors.joining;
...
i -> list.subList(i, min(i + pageSize, list.size()))
.stream()
.map(Object::toString)
.collect(joining(","))
which just collect the elements separated by a comma into a single String.
Simple solution using Guava: com.google.common.collect.Lists#partition:
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)));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With