简体   繁体   中英

Convert a list to list of list using java 8 streams

I have a list and a batch size. I need to split it into a list of lists where each list has a maximum size of batch size preferably using java8 Stream API.

One important condition is, if I have an empty list [] , I need to get [[]] .

I am doing this

final AtomicInteger counter = new AtomicInteger();
List<List<Integer>> result = listToSplit.stream()
    .collect(Collectors.groupingBy(it -> counter.getAndIncrement() / batchSize))
    .values();

But for empty list, it gives [[null]]

Few notes first:

  1. The hack using AtomicInteger or int[0] is familiar to me since I have already asked 2 questions regarding it years ago when I started using Streams:

  2. As I got more familiar with the core principles of Stream API I realized they should be treated a bit differently than a typical for-loops although you still use IntStream that might be close to a procedural for-loop construct. Don't violate the Side-effect principle using Stream API.

  3. One more thing to remind before we get to a solution is that Collectors.groupingBy is a collector resulting in a Map and the subsequent call of Map.values() returns Collection<List<Integer>> instead of List<List<Integer>> .

  4. A final note is a reminder, that the solution for an empty input list is not compatible with the behavior with a list that can be perfectly chunked (a list of 15 elements split to lists by 5 elements). So, if an empty array results in [[]] , then the result [[1,2,3],[4,5,6],[]] of a list 1,2,3,4,5,6 with batchSize=3 is not acceptable but likely generated with a simple non-branched approach.

With regard to what was said above, you might conclude to this solution, that is not elegant though:

int max = (int) Math.ceil((double) listToSplit.size() / batchSize);   // number of chunks

List<List<Integer>> result;

if (listToSplit.isEmpty()) {
    result = Arrays.asList(Collections.emptyList());          // results in [[]] if empty
} else {
    result = IntStream.range(0, max)
        .map(i -> i * batchSize)                              // initial indices
        .mapToObj(i -> listToSplit.subList(                   // find a sublist
            i, Math.min(i + batchSize, listToSplit.size())))  // .. from i*b to (i+1)*b 
        .collect(Collectors.toList());                        // as List<List<Integer>>
}   

The conclusion is to stick with the old but gold for-loop for such use-case. :)

Used IntStream and list.subList

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ListOfList {

    public static void main(String[] args) {
        List<Integer> listToSplit = new ArrayList<>();
//        listToSplit.add(1);
//        listToSplit.add(1);
//        listToSplit.add(1);
//        listToSplit.add(1);
        double batchSize = 3;

        int loops = Math.max(1,(int) Math.ceil(listToSplit.size()/batchSize));
        final List<List<Integer>> collect = IntStream.iterate(-1, i -> (int) (i + batchSize)).limit(loops)
                .mapToObj(id -> {
                    int actualCounter  = id+1;
                    int finalLength = (int) Math.min(listToSplit.size(),actualCounter + batchSize);
                    return listToSplit.subList(actualCounter, finalLength);
                }).collect(Collectors.toList());
        System.out.println(collect);
    }
}

If you're open to using a third-party library, you can use Collectors2.chunk() from Eclipse Collections with a Java Stream.

@Test
public void chunk()
{
    List<Integer> listToSplit = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    List<MutableList<Integer>> chunks = listToSplit.isEmpty() ?
            Lists.mutable.with(Lists.mutable.empty()) :
            listToSplit.stream().collect(Collectors2.chunk(3));

    List<List<Integer>> expected = Arrays.asList(
            Arrays.asList(1, 2, 3),
            Arrays.asList(4, 5, 6),
            Arrays.asList(7, 8, 9),
            Arrays.asList(10));
    Assert.assertEquals(expected, chunks);
}

Note: I am a committer for Eclipse Collections.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM