Split list into multiple lists with fixed number of elements in java 8
You can use Guava library.
List<Integer> bigList = ...
List<List<Integer>> smallerLists = Lists.partition(bigList, 10);
It sounds like a problem that is better handled like a low-level Stream
operation just like the ops provided by the Stream
API itself. A (relative) simple solution may look like:
public static <T> Stream<List<T>> chunked(Stream<T> s, int chunkSize) {
if(chunkSize<1) throw new IllegalArgumentException("chunkSize=="+chunkSize);
if(chunkSize==1) return s.map(Collections::singletonList);
Spliterator<T> src=s.spliterator();
long size=src.estimateSize();
if(size!=Long.MAX_VALUE) size=(size+chunkSize-1)/chunkSize;
int ch=src.characteristics();
ch&=Spliterator.SIZED|Spliterator.ORDERED|Spliterator.DISTINCT|Spliterator.IMMUTABLE;
ch|=Spliterator.NONNULL;
return StreamSupport.stream(new Spliterators.AbstractSpliterator<List<T>>(size, ch)
{
private List<T> current;
@Override
public boolean tryAdvance(Consumer<? super List<T>> action) {
if(current==null) current=new ArrayList<>(chunkSize);
while(current.size()<chunkSize && src.tryAdvance(current::add));
if(!current.isEmpty()) {
action.accept(current);
current=null;
return true;
}
return false;
}
}, s.isParallel());
}
Simple test:
chunked(Stream.of(1, 2, 3, 4, 5, 6, 7), 3)
.parallel().forEachOrdered(System.out::println);
The advantage is that you do not need a full collection of all items for subsequent stream processing, e.g.
chunked(
IntStream.range(0, 1000).mapToObj(i -> {
System.out.println("processing item "+i);
return i;
}), 2).anyMatch(list->list.toString().equals("[6, 7]")));
will print:
processing item 0
processing item 1
processing item 2
processing item 3
processing item 4
processing item 5
processing item 6
processing item 7
true
rather than processing a thousand items of IntStream.range(0, 1000)
. This also enables using infinite source Stream
s:
chunked(Stream.iterate(0, i->i+1), 2).anyMatch(list->list.toString().equals("[6, 7]")));
If you are interested in a fully materialized collection rather than applying subsequent Stream
operations, you may simply use the following operation:
List<Integer> list=Arrays.asList(1, 2, 3, 4, 5, 6, 7);
int listSize=list.size(), chunkSize=2;
List<List<Integer>> list2=
IntStream.range(0, (listSize-1)/chunkSize+1)
.mapToObj(i->list.subList(i*=chunkSize,
listSize-chunkSize>=i? i+chunkSize: listSize))
.collect(Collectors.toList());
You can create your own collector. Something like this:
class GroupingCollector<T> implements Collector<T, List<List<T>>, List<List<T>>> {
private final int elementCountInGroup;
public GroupingCollector(int elementCountInGroup) {
this.elementCountInGroup = elementCountInGroup;
}
@Override
public Supplier<List<List<T>>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<List<T>>, T> accumulator() {
return (lists, integer) -> {
if (!lists.isEmpty()) {
List<T> integers = lists.get(lists.size() - 1);
if (integers.size() < elementCountInGroup) {
integers.add(integer);
return;
}
}
List<T> list = new ArrayList<>();
list.add(integer);
lists.add(list);
};
}
@Override
public BinaryOperator<List<List<T>>> combiner() {
return (lists, lists2) -> {
List<List<T>> r = new ArrayList<>();
r.addAll(lists);
r.addAll(lists2);
return r;
};
}
@Override
public Function<List<List<T>>, List<List<T>>> finisher() {
return lists -> lists;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
And then you can use it in a way like this:
List<List<Integer>> collect = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(new GroupingCollector<>(3));
System.out.println(collect);
Will print:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]