在 java 8 中将列表拆分为具有固定数量元素的多个列表
Split list into multiple lists with fixed number of elements in java 8
我想要类似于 Scala 分组函数的东西。基本上,一次选择 2 个元素并处理它们。这是相同的参考:
Split list into multiple lists with fixed number of elements
Lambda 确实提供了 groupingBy 和 partitioningBy 之类的东西,但是 none 它们似乎与 Scala 中的分组函数一样。任何指针将不胜感激。
您可以编写自己的收集器终结器,类似于
final List<String> strings = Arrays.asList("Hello", "World", "I", "Am", "You");
final int size = 3;
final List<List<String>> stringLists = strings.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), new Function<List<String>, List<List<String>>>() {
@Override
public List<List<String>> apply(List<String> strings) {
final List<List<String>> result = new ArrayList<>();
int counter = 0;
List<String> stringsToAdd = new ArrayList<>();
for (final String string : strings) {
if (counter == 0) {
result.add(stringsToAdd);
} else {
if (counter == size) {
stringsToAdd = new ArrayList<>();
result.add(stringsToAdd);
counter = 0;
}
}
++counter;
stringsToAdd.add(string);
}
return result;
}
}));
System.out.println("stringLists = " + stringLists); // stringLists = [[Hello, World, I], [Am, You]]
您可以创建自己的收集器。像这样:
class GroupingCollector<T> implements Collector<T, List<List<T>>, List<List<T>>> {
private final int elementCountInGroup;
public GroupingCollector(int elementCountInGroup) {
this.elementCountInGroup = elementCountInGroup;
}
@Override
public Supplier<List<List<T>>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<List<T>>, T> accumulator() {
return (lists, integer) -> {
if (!lists.isEmpty()) {
List<T> integers = lists.get(lists.size() - 1);
if (integers.size() < elementCountInGroup) {
integers.add(integer);
return;
}
}
List<T> list = new ArrayList<>();
list.add(integer);
lists.add(list);
};
}
@Override
public BinaryOperator<List<List<T>>> combiner() {
return (lists, lists2) -> {
List<List<T>> r = new ArrayList<>();
r.addAll(lists);
r.addAll(lists2);
return r;
};
}
@Override
public Function<List<List<T>>, List<List<T>>> finisher() {
return lists -> lists;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
然后你可以像这样使用它:
List<List<Integer>> collect = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(new GroupingCollector<>(3));
System.out.println(collect);
将打印:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
这听起来像是一个像低级 Stream
操作一样更好地处理的问题,就像 Stream
API 本身提供的操作一样。一个(相对)简单的解决方案可能如下所示:
public static <T> Stream<List<T>> chunked(Stream<T> s, int chunkSize) {
if(chunkSize<1) throw new IllegalArgumentException("chunkSize=="+chunkSize);
if(chunkSize==1) return s.map(Collections::singletonList);
Spliterator<T> src=s.spliterator();
long size=src.estimateSize();
if(size!=Long.MAX_VALUE) size=(size+chunkSize-1)/chunkSize;
int ch=src.characteristics();
ch&=Spliterator.SIZED|Spliterator.ORDERED|Spliterator.DISTINCT|Spliterator.IMMUTABLE;
ch|=Spliterator.NONNULL;
return StreamSupport.stream(new Spliterators.AbstractSpliterator<List<T>>(size, ch)
{
private List<T> current;
@Override
public boolean tryAdvance(Consumer<? super List<T>> action) {
if(current==null) current=new ArrayList<>(chunkSize);
while(current.size()<chunkSize && src.tryAdvance(current::add));
if(!current.isEmpty()) {
action.accept(current);
current=null;
return true;
}
return false;
}
}, s.isParallel());
}
简单测试:
chunked(Stream.of(1, 2, 3, 4, 5, 6, 7), 3)
.parallel().forEachOrdered(System.out::println);
优点是你不需要所有项目的完整集合用于后续流处理,例如
chunked(
IntStream.range(0, 1000).mapToObj(i -> {
System.out.println("processing item "+i);
return i;
}), 2).anyMatch(list->list.toString().equals("[6, 7]")));
将打印:
processing item 0
processing item 1
processing item 2
processing item 3
processing item 4
processing item 5
processing item 6
processing item 7
true
而不是处理 IntStream.range(0, 1000)
的一千个项目。这也允许使用无限源 Stream
s:
chunked(Stream.iterate(0, i->i+1), 2).anyMatch(list->list.toString().equals("[6, 7]")));
如果您对完全具体化的集合感兴趣而不是应用后续的 Stream
操作,您可以简单地使用以下操作:
List<Integer> list=Arrays.asList(1, 2, 3, 4, 5, 6, 7);
int listSize=list.size(), chunkSize=2;
List<List<Integer>> list2=
IntStream.range(0, (listSize-1)/chunkSize+1)
.mapToObj(i->list.subList(i*=chunkSize,
listSize-chunkSize>=i? i+chunkSize: listSize))
.collect(Collectors.toList());
您可以使用 Guava 库。
List<Integer> bigList = ...
List<List<Integer>> smallerLists = Lists.partition(bigList, 10);
将列表转换为列表列表的递归解决方案也是可能的
int chunkSize = 2;
private <T> List<List<T>> process(List<T> list) {
if (list.size() > chunkSize) {
List<T> chunk = list.subList(0, chunkSize);
List<T> rest = list.subList(chunkSize, list.size());
List<List<T>> lists = process(rest);
return concat(chunk, lists);
} else {
ArrayList<List<T>> retVal = new ArrayList<>();
retVal.add(list);
return retVal;
}
}
private <T> List<List<T>> concat(List<T> chunk, List<List<T>> rest) {
rest.add(0, chunk);
return rest;
}
具有 java 8 个流的简单版本 api:
static <T> List<List<T>> partition(List<T> list, Integer partitionSize) {
int numberOfLists = BigDecimal.valueOf(list.size())
.divide(BigDecimal.valueOf(partitionSize), 0, CEILING)
.intValue();
return IntStream.range(0, numberOfLists)
.mapToObj(it -> list.subList(it * partitionSize, Math.min((it+1) * partitionSize, list.size())))
.collect(Collectors.toList());
}
我想要类似于 Scala 分组函数的东西。基本上,一次选择 2 个元素并处理它们。这是相同的参考:
Split list into multiple lists with fixed number of elements
Lambda 确实提供了 groupingBy 和 partitioningBy 之类的东西,但是 none 它们似乎与 Scala 中的分组函数一样。任何指针将不胜感激。
您可以编写自己的收集器终结器,类似于
final List<String> strings = Arrays.asList("Hello", "World", "I", "Am", "You");
final int size = 3;
final List<List<String>> stringLists = strings.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), new Function<List<String>, List<List<String>>>() {
@Override
public List<List<String>> apply(List<String> strings) {
final List<List<String>> result = new ArrayList<>();
int counter = 0;
List<String> stringsToAdd = new ArrayList<>();
for (final String string : strings) {
if (counter == 0) {
result.add(stringsToAdd);
} else {
if (counter == size) {
stringsToAdd = new ArrayList<>();
result.add(stringsToAdd);
counter = 0;
}
}
++counter;
stringsToAdd.add(string);
}
return result;
}
}));
System.out.println("stringLists = " + stringLists); // stringLists = [[Hello, World, I], [Am, You]]
您可以创建自己的收集器。像这样:
class GroupingCollector<T> implements Collector<T, List<List<T>>, List<List<T>>> {
private final int elementCountInGroup;
public GroupingCollector(int elementCountInGroup) {
this.elementCountInGroup = elementCountInGroup;
}
@Override
public Supplier<List<List<T>>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<List<T>>, T> accumulator() {
return (lists, integer) -> {
if (!lists.isEmpty()) {
List<T> integers = lists.get(lists.size() - 1);
if (integers.size() < elementCountInGroup) {
integers.add(integer);
return;
}
}
List<T> list = new ArrayList<>();
list.add(integer);
lists.add(list);
};
}
@Override
public BinaryOperator<List<List<T>>> combiner() {
return (lists, lists2) -> {
List<List<T>> r = new ArrayList<>();
r.addAll(lists);
r.addAll(lists2);
return r;
};
}
@Override
public Function<List<List<T>>, List<List<T>>> finisher() {
return lists -> lists;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
然后你可以像这样使用它:
List<List<Integer>> collect = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(new GroupingCollector<>(3));
System.out.println(collect);
将打印:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
这听起来像是一个像低级 Stream
操作一样更好地处理的问题,就像 Stream
API 本身提供的操作一样。一个(相对)简单的解决方案可能如下所示:
public static <T> Stream<List<T>> chunked(Stream<T> s, int chunkSize) {
if(chunkSize<1) throw new IllegalArgumentException("chunkSize=="+chunkSize);
if(chunkSize==1) return s.map(Collections::singletonList);
Spliterator<T> src=s.spliterator();
long size=src.estimateSize();
if(size!=Long.MAX_VALUE) size=(size+chunkSize-1)/chunkSize;
int ch=src.characteristics();
ch&=Spliterator.SIZED|Spliterator.ORDERED|Spliterator.DISTINCT|Spliterator.IMMUTABLE;
ch|=Spliterator.NONNULL;
return StreamSupport.stream(new Spliterators.AbstractSpliterator<List<T>>(size, ch)
{
private List<T> current;
@Override
public boolean tryAdvance(Consumer<? super List<T>> action) {
if(current==null) current=new ArrayList<>(chunkSize);
while(current.size()<chunkSize && src.tryAdvance(current::add));
if(!current.isEmpty()) {
action.accept(current);
current=null;
return true;
}
return false;
}
}, s.isParallel());
}
简单测试:
chunked(Stream.of(1, 2, 3, 4, 5, 6, 7), 3)
.parallel().forEachOrdered(System.out::println);
优点是你不需要所有项目的完整集合用于后续流处理,例如
chunked(
IntStream.range(0, 1000).mapToObj(i -> {
System.out.println("processing item "+i);
return i;
}), 2).anyMatch(list->list.toString().equals("[6, 7]")));
将打印:
processing item 0
processing item 1
processing item 2
processing item 3
processing item 4
processing item 5
processing item 6
processing item 7
true
而不是处理 IntStream.range(0, 1000)
的一千个项目。这也允许使用无限源 Stream
s:
chunked(Stream.iterate(0, i->i+1), 2).anyMatch(list->list.toString().equals("[6, 7]")));
如果您对完全具体化的集合感兴趣而不是应用后续的 Stream
操作,您可以简单地使用以下操作:
List<Integer> list=Arrays.asList(1, 2, 3, 4, 5, 6, 7);
int listSize=list.size(), chunkSize=2;
List<List<Integer>> list2=
IntStream.range(0, (listSize-1)/chunkSize+1)
.mapToObj(i->list.subList(i*=chunkSize,
listSize-chunkSize>=i? i+chunkSize: listSize))
.collect(Collectors.toList());
您可以使用 Guava 库。
List<Integer> bigList = ...
List<List<Integer>> smallerLists = Lists.partition(bigList, 10);
将列表转换为列表列表的递归解决方案也是可能的
int chunkSize = 2;
private <T> List<List<T>> process(List<T> list) {
if (list.size() > chunkSize) {
List<T> chunk = list.subList(0, chunkSize);
List<T> rest = list.subList(chunkSize, list.size());
List<List<T>> lists = process(rest);
return concat(chunk, lists);
} else {
ArrayList<List<T>> retVal = new ArrayList<>();
retVal.add(list);
return retVal;
}
}
private <T> List<List<T>> concat(List<T> chunk, List<List<T>> rest) {
rest.add(0, chunk);
return rest;
}
具有 java 8 个流的简单版本 api:
static <T> List<List<T>> partition(List<T> list, Integer partitionSize) {
int numberOfLists = BigDecimal.valueOf(list.size())
.divide(BigDecimal.valueOf(partitionSize), 0, CEILING)
.intValue();
return IntStream.range(0, numberOfLists)
.mapToObj(it -> list.subList(it * partitionSize, Math.min((it+1) * partitionSize, list.size())))
.collect(Collectors.toList());
}