简体   繁体   English

Java 8:如何使用 lambda 将列表转换为列表列表

[英]Java 8: How to turn a list into a list of lists using lambda

I'm trying to split a list into a list of list where each list has a maximum size of 4.我试图将一个列表拆分为一个列表列表,其中每个列表的最大大小为 4。

I would like to know how this is possible to do using lambdas.我想知道如何使用 lambda 来做到这一点。

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.似乎没有一种简单的方法可以使用 lambda 来拆分列表。 While all of the answers are much appreciated, they're also a wonderful example of when lambdas do not simplify things.虽然所有答案都非常受欢迎,但它们也是 lambda 表达式不简化事物的一个很好的例子。

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;

If you REALLY need a lambda it can be done like this.如果你真的需要一个 lambda,它可以像这样完成。 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);
    });

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).流式传输它,使用分组收集:这给出了一个对象映射 -> 列表,提取映射的值并直接传递给任何构造函数(map.values() 给出一个集合而不是一个列表)。

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 .您可以通过使用采用mapSupplier lambda 的groupingBy变体并提供SortedMap来简化此操作。 This should return an EntrySet that iterates in order.这应该返回一个按顺序迭代的EntrySet 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.使用计数器将您的列表项收集到Map<Integer,Object>进行分组。 The counter is held in a single-element array because the lambda can only use local variables if they're final .计数器保存在单元素数组中,因为 lambda 只能使用final局部变量。
  • Getting the map entries as a stream, and sorting by the Integer key.以流的形式获取地图条目,并按Integer键排序。
  • Using Stream::map() to convert the stream of Map.Entry<Integer,Object> into a stream of Object values.使用Stream::map()Map.Entry<Integer,Object>流转换为Object值流。
  • Collecting this into a list.将其收集到列表中。

This doesn't benefit from any "free" parallelisation.这不会从任何“免费”并行化中受益。 It has a memory overhead in the intermediate Map .它在中间Map有内存开销。 It's not particularly easy to read.读起来不是特别容易。


However, I wouldn't do this, just for the sake of using a lambda.但是,我不会这样做,只是为了使用 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.) (你有一个防御性副本new ArrayList<>(listToSplit.subList(...)) 。我没有复制它,因为它并不总是必要的 - 例如,如果输入列表是不可修改的,并且输出列表不是为了是可修改的。但如果您决定在您的情况下需要它,请把它放回去。)

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> :或者,您可以编写自己的(不可修改的) List实现,它是基础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.这将具有对底层列表等效的大 O 访问时间。

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]]

You can use:您可以使用:

ListUtils.partition(List list, int size) ListUtils.partition(List list, int size)

OR

List<List> partition(List list, int size) List<List> 分区(List list, int size)

Both return consecutive sublists of a list, each of the same size (the final list may be smaller).两者都返回一个列表的连续子列表,每个子列表的大小相同(最终列表可能更小)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何将列表列表转换为 Java 8 中的列表? - How can I turn a List of Lists into a List in Java 8? Java,Lambda:如何从具有不同类型的列表集合中查找列表? - Java, Lambda: How to find a List from a Collection of Lists with different Types? 如何使用Java中的lambda函数对每列和每行的列表列表求和? - How to sum a list of lists per column and per row using lambda functions in Java? 在Java中使用清单清单 - using a list of lists in java 如何遍历多个列表并使用Java 8 Lambda表达式合并两个列表中的唯一值来创建另一个列表 - How to loop through multiple Lists and create another list with merging of unique values from both the lists using Java 8 lambda expressions 如何将列表列表转换为 Java 8 中的列表? 没有流,只有 for( 或 foreach) 和 if - How can I turn a List of Lists into a List in Java 8? without streams, only for( or foreach) and if 使用 lambda 对 Java 中的列表进行排序 - Sorting list of list in Java using lambda 如何在 Java 中初始化列表列表? - How to initialize list of lists in Java? Java 8:如何将列表流式传输到列表列表中? - Java 8: How to stream a list into a list of lists? Vavr - 将列表列表变成单个列表 - Vavr - turn List of Lists into single List
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM