从 Java 8 流中取出第 n 个元素
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31602425/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Take every nth element from a Java 8 stream
提问by Michel Kr?mer
Suppose I have a list like this :
假设我有一个这样的列表:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Is it possible to use a Java 8 stream to take every second element from this list to obtain the following?
是否可以使用 Java 8 流从该列表中获取第二个元素以获得以下内容?
[1, 3, 5, 7, 9]
Or maybe even every third element?
或者甚至每三个元素?
[1, 4, 7, 10]
Basically, I'm looking for a function to take every nth element of a stream:
基本上,我正在寻找一个函数来获取流的每个第 n 个元素:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> list2 = list.stream().takenth(3).collect(Collectors.toList());
System.out.println(list2);
// => [1, 4, 7, 10]
采纳答案by sprinter
One of the prime motivations for the introduction of Java streams was to allow parallel operations. This led to a requirement that operations on Java streams such as map
and filter
be independent of the position of the item in the stream or the items around it. This has the advantage of making it easy to split streams for parallel processing. It has the disadvantage of making certain operations more complex.
引入 Java 流的主要动机之一是允许并行操作。这导致要求对 Java 流的操作,例如map
和filter
独立于流中项目的位置或其周围的项目。这样做的优点是可以轻松拆分流以进行并行处理。它的缺点是使某些操作更加复杂。
So the simple answer is that there is no easy way to do things such as take every nth item or map each item to the sum of all previous items.
所以简单的答案是没有简单的方法来做这样的事情,比如取每 n 个项目或将每个项目映射到所有先前项目的总和。
The most straightforward way to implement your requirement is to use the index of the list you are streaming from:
实现您的要求的最直接方法是使用您从中流式传输的列表的索引:
List<String> list = ...;
return IntStream.range(0, list.size())
.filter(n -> n % 3 == 0)
.mapToObj(list::get)
.collect(Collectors.toList());
A more complicated solution would be to create a custom collector that collects every nth item into a list.
一个更复杂的解决方案是创建一个自定义收集器,将每个第 n 个项目收集到一个列表中。
class EveryNth<C> {
private final int nth;
private final List<List<C>> lists = new ArrayList<>();
private int next = 0;
private EveryNth(int nth) {
this.nth = nth;
IntStream.range(0, nth).forEach(i -> lists.add(new ArrayList<>()));
}
private void accept(C item) {
lists.get(next++ % nth).add(item);
}
private EveryNth<C> combine(EveryNth<C> other) {
other.lists.forEach(l -> lists.get(next++ % nth).addAll(l));
next += other.next;
return this;
}
private List<C> getResult() {
return lists.get(0);
}
public static Collector<Integer, ?, List<Integer>> collector(int nth) {
return Collector.of(() -> new EveryNth(nth),
EveryNth::accept, EveryNth::combine, EveryNth::getResult));
}
This could be used as follows:
这可以按如下方式使用:
List<String> list = Arrays.asList("Anne", "Bill", "Chris", "Dean", "Eve", "Fred", "George");
list.stream().parallel().collect(EveryNth.collector(3)).forEach(System.out::println);
Which returns the result you would expect.
这将返回您期望的结果。
This is a very inefficient algorithm even with parallel processing. It splits all items it accepts into n lists and then just returns the first. Unfortunately it has to keep all items through the accumulation process because it's not until they are combined that it knows which list is the nth one.
即使使用并行处理,这也是一种非常低效的算法。它将它接受的所有项目分成 n 个列表,然后只返回第一个。不幸的是,它必须在累积过程中保留所有项目,因为直到将它们组合起来,它才知道哪个列表是第 n 个。
Given the complexity and inefficiency of the collector solution I would definitely recommend sticking with the indices based solution above in preference to this if you can. If you aren't using a collection that supports get
(e.g. you are passed a Stream
rather than a List
) then you will either need to collect the stream using Collectors.toList
or use the EveryNth
solution above.
鉴于收集器解决方案的复杂性和低效率,如果可以的话,我肯定会建议坚持使用上面基于索引的解决方案,而不是这个。如果您不使用支持的集合get
(例如,您传递的是 aStream
而不是 a List
),那么您将需要使用Collectors.toList
或使用上述EveryNth
解决方案来收集流。
回答by Federico Peralta Schaffner
EDIT - Nov 28, 2017
编辑 - 2017 年 11 月 28 日
As user @Emiel suggests in the comments, the best way to do this would be to use Stream.itearate
to drive the list through a sequence of indices:
正如用户@Emiel 在评论中建议的那样,最好的方法是使用Stream.itearate
通过一系列索引来驱动列表:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int skip = 3;
int size = list.size();
// Limit to carefully avoid IndexOutOfBoundsException
int limit = size / skip + Math.min(size % skip, 1);
List<Integer> result = Stream.iterate(0, i -> i + skip)
.limit(limit)
.map(list::get)
.collect(Collectors.toList());
System.out.println(result); // [1, 4, 7, 10]
This approach doesn't have the drawbacks of my previous answer, which comes below (I've decided to keep it for historical reasons).
这种方法没有我之前的答案的缺点,下面是(由于历史原因,我决定保留它)。
Another approach would be to use Stream.iterate()
the following way:
另一种方法是使用Stream.iterate()
以下方式:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int skip = 3;
int size = list.size();
// Limit to carefully avoid IndexOutOfBoundsException
int limit = size / skip + Math.min(size % skip, 1);
List<Integer> result = Stream.iterate(list, l -> l.subList(skip, l.size()))
.limit(limit)
.map(l -> l.get(0))
.collect(Collectors.toList());
System.out.println(result); // [1, 4, 7, 10]
The idea is to create a stream of sublists, each one skipping the first N
elements of the previous one (N=3
in the example).
这个想法是创建一个子列表流,每个子列表都跳过N
前一个(N=3
在示例中)的第一个元素。
We have to limit the number of iterations so that we don't try to get a sublist whose bounds are out of range.
我们必须限制迭代次数,这样我们就不会尝试获取边界超出范围的子列表。
Then, we map our sublists to their first element and collect our results. Keeping the first element of every sublist works as expected because every sublist's begin index is shifted N
elements to the right, according to the source list.
然后,我们将我们的子列表映射到它们的第一个元素并收集我们的结果。N
根据源列表,保留每个子列表的第一个元素按预期工作,因为每个子列表的开始索引都将元素向右移动。
This is also efficient, because the List.sublist()
method returns a viewof the original list, meaning that it doesn't create a new List
for each iteration.
这也很有效,因为该List.sublist()
方法返回原始列表的视图,这意味着它不会List
为每次迭代创建一个新列表。
EDIT:After a while, I've learnt that it's much better to take either one of @sprinter's approachs, since subList()
creates a wrapper around the original list. This means that the second list of the stream would be a wrapper of the first list, the third list of the stream would be a wrapper of the second list (which is already a wrapper!), and so on...
编辑:一段时间后,我了解到采用@sprinter 的任何一种方法都要好得多,因为subList()
在原始列表周围创建了一个包装器。这意味着流的第二个列表将是第一个列表的包装器,流的第三个列表将是第二个列表的包装器(它已经是一个包装器!),依此类推...
While this might work for small to medium-sized lists, it should be noted that for a very large source list, many wrappers would be created. And this might end up being expensive, or even generating a StackOverflowError
.
虽然这可能适用于中小型列表,但应该注意的是,对于非常大的源列表,将创建许多包装器。这最终可能会很昂贵,甚至会生成StackOverflowError
.
回答by Lukas Eder
If you're willing to use a third party library, then jOOλoffers useful features like zipWithIndex()
:
如果您愿意使用第三方库,那么jOOλ提供了一些有用的功能,例如zipWithIndex()
:
Every second element
每第二个元素
System.out.println(
Seq.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.zipWithIndex() // This produces a Tuple2(yourvalue, index)
.filter(t -> t.v2 % 2 == 0) // Filter by the index
.map(t -> t.v1) // Remove the index again
.toList()
);
[1, 3, 5, 7, 9]
Every third element
每三个元素
System.out.println(
Seq.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.zipWithIndex()
.filter(t -> t.v2 % 3 == 0)
.map(t -> t.v1)
.toList()
);
[1, 4, 7, 10]
Disclaimer: I work for the company behind jOOλ
免责声明:我为 jOOλ 背后的公司工作
回答by Xavier
You could also use flatMap
with a custom function that skips items:
您还可以使用flatMap
跳过项目的自定义函数:
private <T> Function<T, Stream<T>> everyNth(int n) {
return new Function<T, Stream<T>>() {
int i = 0;
@Override
public Stream<T> apply(T t) {
if (i++ % n == 0) {
return Stream.of(t);
}
return Stream.empty();
}
};
}
@Test
public void everyNth() {
assertEquals(
Arrays.asList(1, 4, 7, 10),
IntStream.rangeClosed(1, 10).boxed()
.flatMap(everyNth(3))
.collect(Collectors.toList())
);
}
It has the advantage of working with non-indexed streams. But it's not a good idea to use it with parallel streams (maybe switch to an atomic integer for i
).
它具有使用非索引流的优势。但是将它与并行流一起使用并不是一个好主意(可能切换到原子整数i
)。
回答by saka1029
Try this.
尝试这个。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int[] n = {0};
List<Integer> result = list.stream()
.filter(x -> n[0]++ % 3 == 0)
.collect(Collectors.toList());
System.out.println(result);
// -> [1, 4, 7, 10]
回答by user_3380739
Here is code by AbacusUtil
这是AbacusUtil 的代码
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.filter(MutableInt.of(0), (e, idx) -> idx.getAndDecrement() % 2 == 0)
.println();
// output: 1, 3, 5, 7, 9
Or if index required:
或者如果需要索引:
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.indexed().filter(i -> i.index() % 2 == 0).println();
// output: [0]=1, [2]=3, [4]=5, [6]=7, [8]=9
Declaration: I'm the developer of AbacusUtil.
声明: 我是 AbacusUtil 的开发者。
回答by ZhekaKozlov
Use Guava:
使用番石榴:
Streams
.mapWithIndex(stream, SimpleImmutableEntry::new)
.filter(entry -> entry.getValue() % 3 == 0)
.map(Entry::getKey)
.collect(Collectors.toList());
回答by mimi
Can you try this
你能试试这个吗
employees.stream()
.filter(e -> e.getName().charAt(0) == 's')
.skip(n-1)
.findFirst()
回答by Jens Schauder
I'm coming here from How to avoid memory overflow using high throughput JAVA I/O Stream from JDBC connectors?which suggests you are concerned about foot print.
我来自如何使用来自 JDBC 连接器的高吞吐量 JAVA I/O 流避免内存溢出?这表明您担心脚印。
I therefore suggest the following solution which should have a small rate of garbage collection
因此,我建议以下解决方案,它应该具有较小的垃圾收集率
int[] counter = new int[]{0};
list.stream()
.filter(l -> counter[0]++ % n == 0)
Of course you need to ensure that your stream isn't parallel.
当然,您需要确保您的流不是并行的。