Java 8: How to turn a list into a list of lists us

2019-04-07 10:43发布

I'm trying to split a list into a list of list where each list has a maximum size of 4.

I would like to know how this is possible to do using lambdas.

Currently the way I'm doing it is as follow:

List<List<Object>> listOfList = new ArrayList<>();

final int MAX_ROW_LENGTH = 4;
int startIndex =0;
while(startIndex <= listToSplit.size() )    
{
    int endIndex = ( ( startIndex+MAX_ROW_LENGTH ) <  listToSplit.size() ) ? startIndex+MAX_ROW_LENGTH : listToSplit.size();
    listOfList.add(new ArrayList<>(listToSplit.subList(startIndex, endIndex)));
    startIndex = startIndex+MAX_ROW_LENGTH;
}

UPDATE

It seems that there isn't a simple way to use lambdas to split lists. While all of the answers are much appreciated, they're also a wonderful example of when lambdas do not simplify things.

5条回答
Viruses.
2楼-- · 2019-04-07 11:07

Surely the below is sufficient

final List<List<Object>> listOfList = new ArrayList<>(
            listToSplit.stream()
                    .collect(Collectors.groupingBy(el -> listToSplit.indexOf(el) / MAX_ROW_LENGTH))
                    .values()
    );

Stream it, collect with a grouping: this gives a Map of Object -> List, pull the values of the map and pass directly into whatever constructor (map.values() gives a Collection not a List).

查看更多
何必那么认真
3楼-- · 2019-04-07 11:13

Perhaps you can use something like that

 BiFunction<List,Integer,List> splitter= (list2, count)->{
            //temporary list of lists
            List<List> listOfLists=new ArrayList<>();

            //helper implicit recursive function
            BiConsumer<Integer,BiConsumer> splitterHelper = (offset, func) -> {
                if(list2.size()> offset+count){
                    listOfLists.add(list2.subList(offset,offset+count));

                    //implicit self call
                    func.accept(offset+count,func);
                }
                else if(list2.size()>offset){
                    listOfLists.add(list2.subList(offset,list2.size()));

                    //implicit self call
                    func.accept(offset+count,func);
                }
            };

            //pass self reference
            splitterHelper.accept(0,splitterHelper);

            return listOfLists;
        };

Usage example

List<Integer> list=new ArrayList<Integer>(){{
            add(1);
            add(2);
            add(3);
            add(4);
            add(5);
            add(6);
            add(7);
            add(8);
            add(8);
        }};

        //calling splitter function
        List listOfLists = splitter.apply(list, 3 /*max sublist size*/);

        System.out.println(listOfLists);

And as a result we have

[[1, 2, 3], [4, 5, 6], [7, 8, 8]]
查看更多
相关推荐>>
4楼-- · 2019-04-07 11:17

If you REALLY need a lambda it can be done like this. Otherwise the previous answers are better.

    List<List<Object>> lists = new ArrayList<>();
    AtomicInteger counter = new AtomicInteger();
    final int MAX_ROW_LENGTH = 4;
    listToSplit.forEach(pO -> {
        if(counter.getAndIncrement() % MAX_ROW_LENGTH == 0) {
            lists.add(new ArrayList<>());
        }
        lists.get(lists.size()-1).add(pO);
    });
查看更多
Rolldiameter
5楼-- · 2019-04-07 11:22

Try this approach:

static <T> List<List<T>> listSplitter(List<T> incoming, int size) {
    // add validation if needed
    return incoming.stream()
            .collect(Collector.of(
                    ArrayList::new,
                    (accumulator, item) -> {
                        if(accumulator.isEmpty()) {
                            accumulator.add(new ArrayList<>(singletonList(item)));
                        } else {
                            List<T> last = accumulator.get(accumulator.size() - 1);
                            if(last.size() == size) {
                                accumulator.add(new ArrayList<>(singletonList(item)));
                            } else {
                                last.add(item);
                            }
                        }
                    },
                    (li1, li2) -> {
                        li1.addAll(li2);
                        return li1;
                    }
            ));
}
System.out.println(
        listSplitter(
                Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
                4
        )
);

Also note that this code could be optimized, instead of:

new ArrayList<>(Collections.singletonList(item))

use this one:

List<List<T>> newList = new ArrayList<>(size);
newList.add(item);
return newList;
查看更多
Root(大扎)
6楼-- · 2019-04-07 11:31

The requirement is a bit odd, but you could do:

final int[] counter = new int[] {0};

List<List<Object>> listOfLists = in.stream()
   .collect(Collectors.groupingBy( x -> counter[0]++ / MAX_ROW_LENGTH ))
   .entrySet().stream()
   .sorted(Map.Entry.comparingByKey())
   .map(Map.Entry::getValue)
   .collect(Collectors.toList());

You could probably streamline this by using the variant of groupingBy that takes a mapSupplier lambda, and supplying a SortedMap. This should return an EntrySet that iterates in order. I leave it as an exercise.

What we're doing here is:

  • Collecting your list items into a Map<Integer,Object> using a counter to group. The counter is held in a single-element array because the lambda can only use local variables if they're final.
  • Getting the map entries as a stream, and sorting by the Integer key.
  • Using Stream::map() to convert the stream of Map.Entry<Integer,Object> into a stream of Object values.
  • Collecting this into a list.

This doesn't benefit from any "free" parallelisation. It has a memory overhead in the intermediate Map. It's not particularly easy to read.


However, I wouldn't do this, just for the sake of using a lambda. I would do something like:

for(int i=0; i<in.size(); i += MAX_ROW_LENGTH) {
    listOfList.add(
        listToSplit.subList(i, Math.min(i + MAX_ROW_LENGTH, in.size());
}

(Yours had a defensive copy new ArrayList<>(listToSplit.subList(...)). I've not duplicated it because it's not always necessary - for example if the input list is unmodifiable and the output lists aren't intended to be modifiable. But do put it back in if you decide you need it in your case.)

This will be extremely fast on any in-memory list. You're very unlikely to want to parallelise it.


Alternatively, you could write your own (unmodifiable) implementation of List that's a view over the underlying List<Object>:

public class PartitionedList<T> extends AbstractList<List<T>> {

    private final List<T> source;
    private final int sublistSize;

    public PartitionedList(T source, int sublistSize) {
       this.source = source;
       this.sublistSize = sublistSize;
    }

    @Override
    public int size() {
       return source.size() / sublistSize;
    }

    @Override
    public List<T> get(int index) {
       int sourceIndex = index * sublistSize
       return source.subList(sourceIndex, 
                             Math.min(sourceIndex + sublistSize, source.size());
    }
}

Again, it's up to you whether you want to make defensive copies here.

This will be have equivalent big-O access time to the underlying list.

查看更多
登录 后发表回答