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

时间:2017-08-22 12:09:26

标签: java lambda java-8

我正在尝试将列表拆分为列表,其中每个列表的最大大小为4.

我想知道如何使用lambdas。

目前我正在这样做的方式如下:

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;
}

更新

似乎没有一种简单的方法可以使用lambdas来拆分列表。虽然所有的答案都非常受欢迎,但它们也是lambdas不简化事物的一个很好的例子。

6 个答案:

答案 0 :(得分:5)

尝试这种方法:

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
        )
);

另请注意,此代码可以进行优化,而不是:

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

使用这个:

List<List<T>> newList = new ArrayList<>(size);
newList.add(item);
return newList;

答案 1 :(得分:3)

如果你真的需要一个lambda,可以这样做。否则以前的答案会更好。

    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);
    });

答案 2 :(得分:2)

要求有点奇怪,但你可以这样做:

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());

您可以通过使用groupingBy变体mapSupplier lambda并提供SortedMap来简化此操作。这应返回按顺序迭代的EntrySet。我把它留作练习。

我们在这里做的是:

  • 使用计数器组将您的列表项收集到Map<Integer,Object>。计数器保存在单个元素数组中,因为lambda只能使用局部变量final
  • 将地图条目作为流,并按Integer键排序。
  • 使用Stream::map()Map.Entry<Integer,Object>的流转换为Object值的流。
  • 将此收集到列表中。

这不会受益于任何“免费”并行化。它在中间Map中有内存开销。这不是特别容易阅读。

但是,我不会这样做,只是为了使用lambda。我会做类似的事情:

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

(你的防御副本new ArrayList<>(listToSplit.subList(...))。我没有重复它,因为它并不总是必要的 - 例如,如果输入列表是不可修改的,并且输出列表不是可修改的。如果你决定在你的情况下需要它,请回来。)

在任何内存列表中都会非常快。你不太可能想要并行化。

或者,您可以编写自己的(不可修改的)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());
    }
}

再次,你是否想在这里制作防御性副本取决于你。

这将与基础列表具有相同的大O访问时间。

答案 3 :(得分:2)

也许你可以使用类似的东西

 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;
        };

用法示例

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);

结果我们有了

[[1, 2, 3], [4, 5, 6], [7, 8, 8]]

答案 4 :(得分:2)

当然,以下就足够了

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

流式传输,使用分组进行收集:这会给出一个对象地图 - &gt;列表,拉取地图的值并直接传递到任何构造函数(map.values()给出Collection而不是List。)

答案 5 :(得分:1)

您可以使用:

ListUtils.partition(List list, int size)

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

两者都返回一个列表的连续子列表,每个子列表的大小相同(最终列表可能更小)。