在 Java 8 中是否有一种简洁的方法来迭代带有索引的流?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18552005/
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
Is there a concise way to iterate over a stream with indices in Java 8?
提问by Graeme Moss
Is there a concise way to iterate over a stream whilst having access to the index in the stream?
是否有一种简洁的方法可以在访问流中的索引的同时迭代流?
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList;
Stream<Integer> indices = intRange(1, names.length).boxed();
nameList = zip(indices, stream(names), SimpleEntry::new)
.filter(e -> e.getValue().length() <= e.getKey())
.map(Entry::getValue)
.collect(toList());
which seems rather disappointing compared to the LINQ example given there
与那里给出的 LINQ 示例相比,这似乎相当令人失望
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
Is there a more concise way?
有没有更简洁的方法?
Further it seems the zip has either moved or been removed...
此外,似乎拉链已经移动或被移除......
采纳答案by assylias
The cleanest way is to start from a stream of indices:
最干净的方法是从索引流开始:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0, names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
The resulting list contains "Erik" only.
结果列表仅包含“Erik”。
One alternative which looks more familiar when you are used to for loops would be to maintain an ad hoc counter using a mutable object, for example an AtomicInteger
:
当您习惯于 for 循环时,看起来更熟悉的一种替代方法是使用可变对象维护临时计数器,例如AtomicInteger
:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
AtomicInteger index = new AtomicInteger();
List<String> list = Arrays.stream(names)
.filter(n -> n.length() <= index.incrementAndGet())
.collect(Collectors.toList());
Note that using the latter method on a parallel stream could break as the items would not necesarily be processed "in order".
请注意,在并行流上使用后一种方法可能会中断,因为不必“按顺序”处理项目。
回答by Josh M
There isn't a way to iterate over a Stream
whilst having access to the index because a Stream
is unlike any Collection
. A Stream
is merely a pipeline for carrying data from one place to another, as stated in the documentation:
没有办法Stream
在访问索引的同时迭代 a ,因为 aStream
与 any 不同Collection
。AStream
只是将数据从一个地方传送到另一个地方的管道,如文档中所述:
No storage. A stream is not a data structure that stores elements; instead, they carry values from a source (which could be a data structure, a generator, an IO channel, etc) through a pipeline of computational operations.
没有存储。流不是存储元素的数据结构;相反,它们通过计算操作的管道从源(可以是数据结构、生成器、IO 通道等)携带值。
Of course, as you appear to be hinting at in your question, you could always convert your Stream<V>
to a Collection<V>
, such as a List<V>
, in which you will have access to the indexes.
当然,正如您在问题中似乎暗示的那样,您始终可以将您的转换Stream<V>
为 a Collection<V>
,例如 a List<V>
,您可以在其中访问索引。
回答by Stuart Marks
The Java 8 streams API lacks the features of getting the index of a stream element as well as the ability to zip streams together. This is unfortunate, as it makes certain applications (like the LINQ challenges) more difficult than they would be otherwise.
Java 8 流 API 缺乏获取流元素索引的功能以及将流压缩在一起的能力。这是不幸的,因为它使某些应用程序(如 LINQ 挑战)比其他情况更困难。
There are often workarounds, however. Usually this can be done by "driving" the stream with an integer range, and taking advantage of the fact that the original elements are often in an array or in a collection accessible by index. For example, the Challenge 2 problem can be solved this way:
但是,通常有解决方法。通常这可以通过使用整数范围“驱动”流来完成,并利用原始元素通常位于数组或可通过索引访问的集合中的事实。例如,挑战 2 问题可以这样解决:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList =
IntStream.range(0, names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(toList());
As I mentioned above, this takes advantage of the fact that the data source (the names array) is directly indexable. If it weren't, this technique wouldn't work.
正如我上面提到的,这利用了数据源(名称数组)可直接索引的事实。如果不是这样,这种技术就行不通了。
I'll admit that this doesn't satisfy the intent of Challenge 2. Nonetheless it does solve the problem reasonably effectively.
我承认这并不能满足挑战 2 的意图。尽管如此,它确实合理有效地解决了问题。
EDIT
编辑
My previous code example used flatMap
to fuse the filter and map operations, but this was cumbersome and provided no advantage. I've updated the example per the comment from Holger.
我之前的代码示例用于flatMap
融合过滤器和映射操作,但这很麻烦并且没有任何优势。我已经根据 Holger 的评论更新了示例。
回答by user1195526
I've used the following solution in my project. I think it is better than using mutable objects or integer ranges.
我在我的项目中使用了以下解决方案。我认为它比使用可变对象或整数范围更好。
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Objects.requireNonNull;
public class CollectionUtils {
private CollectionUtils() { }
/**
* Converts an {@link java.util.Iterator} to {@link java.util.stream.Stream}.
*/
public static <T> Stream<T> iterate(Iterator<? extends T> iterator) {
int characteristics = Spliterator.ORDERED | Spliterator.IMMUTABLE;
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false);
}
/**
* Zips the specified stream with its indices.
*/
public static <T> Stream<Map.Entry<Integer, T>> zipWithIndex(Stream<? extends T> stream) {
return iterate(new Iterator<Map.Entry<Integer, T>>() {
private final Iterator<? extends T> streamIterator = stream.iterator();
private int index = 0;
@Override
public boolean hasNext() {
return streamIterator.hasNext();
}
@Override
public Map.Entry<Integer, T> next() {
return new AbstractMap.SimpleImmutableEntry<>(index++, streamIterator.next());
}
});
}
/**
* Returns a stream consisting of the results of applying the given two-arguments function to the elements of this stream.
* The first argument of the function is the element index and the second one - the element value.
*/
public static <T, R> Stream<R> mapWithIndex(Stream<? extends T> stream, BiFunction<Integer, ? super T, ? extends R> mapper) {
return zipWithIndex(stream).map(entry -> mapper.apply(entry.getKey(), entry.getValue()));
}
public static void main(String[] args) {
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
System.out.println("Test zipWithIndex");
zipWithIndex(Arrays.stream(names)).forEach(entry -> System.out.println(entry));
System.out.println();
System.out.println("Test mapWithIndex");
mapWithIndex(Arrays.stream(names), (Integer index, String name) -> index+"="+name).forEach((String s) -> System.out.println(s));
}
}
回答by 42n4
With https://github.com/poetix/protonpacku can do that zip:
使用https://github.com/poetix/protonpack你可以做那个 zip:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList;
Stream<Integer> indices = IntStream.range(0, names.length).boxed();
nameList = StreamUtils.zip(indices, stream(names),SimpleEntry::new)
.filter(e -> e.getValue().length() <= e.getKey()).map(Entry::getValue).collect(toList());
System.out.println(nameList);
回答by John McClean
In addition to protonpack, jOOλ's Seqprovides this functionality (and by extension libraries that build on it like cyclops-react, I am the author of this library).
除了 protonpack 之外,jOOλ 的 Seq 也提供了这个功能(通过像cyclops-react这样的扩展库,我是这个库的作者)。
Seq.seq(Stream.of(names)).zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
Seq also supports just Seq.of(names) and will build a JDK Stream under the covers.
Seq 也只支持 Seq.of(names) 并将在幕后构建一个 JDK Stream。
The simple-react equivalent would similarly look like
简单的反应等价物同样看起来像
LazyFutureStream.of(names)
.zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
The simple-react version is more tailored for asynchronous / concurrent processing.
simple-react 版本更适合异步/并发处理。
回答by Tagir Valeev
Just for completeness here's the solution involving my StreamExlibrary:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
.filterKeyValue((idx, str) -> str.length() <= idx+1)
.values().toList();
Here we create an EntryStream<Integer, String>
which extends Stream<Entry<Integer, String>>
and adds some specific operations like filterKeyValue
or values
. Also toList()
shortcut is used.
在这里,我们创建了一个EntryStream<Integer, String>
,它扩展Stream<Entry<Integer, String>>
并添加了一些特定的操作,如filterKeyValue
或values
。还使用了toList()
快捷方式。
回答by alexpfx
You can create a static inner class to encapsulate the indexer as I needed to do in example below:
您可以创建一个静态内部类来封装索引器,就像我在下面的示例中需要做的那样:
static class Indexer {
int i = 0;
}
public static String getRegex() {
EnumSet<MeasureUnit> range = EnumSet.allOf(MeasureUnit.class);
StringBuilder sb = new StringBuilder();
Indexer indexer = new Indexer();
range.stream().forEach(
measureUnit -> {
sb.append(measureUnit.acronym);
if (indexer.i < range.size() - 1)
sb.append("|");
indexer.i++;
}
);
return sb.toString();
}
回答by Donald Raab
If you don't mind using a third-party library, Eclipse Collectionshas zipWithIndex
and forEachWithIndex
available for use across many types. Here's a set of solutions to this challenge for both JDK types and Eclipse Collections types using zipWithIndex
.
如果你不介意使用第三方库,Eclipse中收藏有zipWithIndex
和forEachWithIndex
可用于跨多种类型使用。以下是针对 JDK 类型和 Eclipse 集合类型使用zipWithIndex
.
String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
ImmutableList<String> expected = Lists.immutable.with("Erik");
Predicate<Pair<String, Integer>> predicate =
pair -> pair.getOne().length() <= pair.getTwo() + 1;
// JDK Types
List<String> strings1 = ArrayIterate.zipWithIndex(names)
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings1);
List<String> list = Arrays.asList(names);
List<String> strings2 = ListAdapter.adapt(list)
.zipWithIndex()
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings2);
// Eclipse Collections types
MutableList<String> mutableNames = Lists.mutable.with(names);
MutableList<String> strings3 = mutableNames.zipWithIndex()
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings3);
ImmutableList<String> immutableNames = Lists.immutable.with(names);
ImmutableList<String> strings4 = immutableNames.zipWithIndex()
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings4);
MutableList<String> strings5 = mutableNames.asLazy()
.zipWithIndex()
.collectIf(predicate, Pair::getOne, Lists.mutable.empty());
Assert.assertEquals(expected, strings5);
Here's a solution using forEachWithIndex
instead.
这是使用forEachWithIndex
代替的解决方案。
MutableList<String> mutableNames =
Lists.mutable.with("Sam", "Pamela", "Dave", "Pascal", "Erik");
ImmutableList<String> expected = Lists.immutable.with("Erik");
List<String> actual = Lists.mutable.empty();
mutableNames.forEachWithIndex((name, index) -> {
if (name.length() <= index + 1)
actual.add(name);
});
Assert.assertEquals(expected, actual);
If you change the lambdas to anonymous inner classes above, then all of these code examples will work in Java 5 - 7 as well.
如果您将 lambdas 更改为上面的匿名内部类,那么所有这些代码示例也将在 Java 5 - 7 中工作。
Note:I am a committer for Eclipse Collections
注意:我是 Eclipse Collections 的提交者
回答by user_3380739
Here is code by AbacusUtil
这是AbacusUtil 的代码
Stream.of(names).indexed()
.filter(e -> e.value().length() <= e.index())
.map(Indexed::value).toList();
Disclosure: I'm the developer of AbacusUtil.
披露: 我是 AbacusUtil 的开发者。