[英]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:我们在这里做的是:
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
局部变量。Integer
key.以流的形式获取地图条目,并按Integer
键排序。Stream::map()
to convert the stream of Map.Entry<Integer,Object>
into a stream of Object
values.使用Stream::map()
将Map.Entry<Integer,Object>
流转换为Object
值流。 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.