有没有一个简洁的方式来迭代在Java 8中的索引stream?
有一个简洁的方式来迭代一个stream,同时访问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());
与那里给出的LINQ例子相比,这看起来相当令人失望
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" }; var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
有没有更简洁的方法?
此外,似乎拉链已经移动或被删除…
最简洁的方法是从一系列索引开始:
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());
结果列表只包含“Erik”。
当你习惯于循环的时候,看起来比较熟悉的另一种方法是使用一个可变对象(例如一个AtomicInteger
来维护一个ad hoc计数器:
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());
请注意, 在并行stream上使用后一种方法可能会中断,因为这些项目将不会“按顺序”处理 。
Java 8streamAPI缺less获取stream元素索引的function以及将stream压缩到一起的function。 这是不幸的,因为它使得某些应用程序(如LINQ挑战)比其他应用程序更困难。
然而,通常有解决方法。 通常这可以通过用整数范围“驱动”stream来完成,并利用原始元素通常位于数组中或索引中可访问的集合的事实。 例如,挑战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());
如上所述,这利用了数据源(名称数组)可直接索引的事实。 如果不是,这种技术是行不通的。
我承认,这并不能满足挑战2的目的。但它确实能够合理有效地解决问题。
编辑
我以前的代码示例使用flatMap
来融合filter和映射操作,但这很麻烦,没有任何优势。 我已经根据Holger的评论更新了这个例子。
我在我的项目中使用了以下解决scheme。 我认为这比使用可变对象或整数范围更好。
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)); } }
除了protonpack之外, jOOλ的Seq提供了这个function(并且通过构build它的扩展库像独眼巨人 – 反应 ,我是这个库的作者)。
Seq.seq(Stream.of(names)).zipWithIndex() .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1) .toList();
Seq也只支持Seq.of(names),并将在下面构build一个JDK Stream。
简单的反应等价物会类似
LazyFutureStream.of(names) .zipWithIndex() .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1) .toList();
简单反应版本更适合asynchronous/并发处理。
为了完整起见,这里是涉及我的StreamEx库的解决scheme:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; EntryStream.of(names) .filterKeyValue((idx, str) -> str.length() <= idx+1) .values().toList();
这里我们创build一个EntryStream<Integer, String>
,它扩展了Stream<Entry<Integer, String>>
并添加了一些特定的操作,如filterKeyValue
或values
。 还使用toList()
快捷方式。
既然番石榴21,你可以使用
Streams.mapWithIndex()
示例(来自官方文档 ):
Streams.mapWithIndex( Stream.of("a", "b", "c"), (str, index) -> str + ":" + index) ) // will return Stream.of("a:0", "b:1", "c:2")
没有办法迭代一个Stream
同时访问索引,因为一个Stream
是不同的任何Collection
。 Stream
只是一个将数据从一个地方传输到另一个地方的pipe道,正如文档中所述:
没有存储。 stream不是存储元素的数据结构; 相反,它们通过计算操作stream水线来源(可能是数据结构,生成器,IO通道等)的值。
当然,因为你似乎暗示了你的问题,所以你总是可以把你的Stream<V>
转换成一个Collection<V>
,比如List<V>
,你可以访问索引。
随着列表,你可以尝试
List<String> strings = new ArrayList<>(Arrays.asList("First", "Second", "Third", "Fourth", "Fifth")); // An example list of Strings strings.stream() // Turn the list into a Stream .collect(HashMap::new, (h, o) -> h.put(h.size(), o), (h, o) -> {}) // Create a map of the index to the object .forEach((i, o) -> { // Now we can use a BiConsumer forEach! System.out.println(String.format("%d => %s", i, o)); });
输出:
0 => First 1 => Second 2 => Third 3 => Fourth 4 => Fifth
与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);
如果您不介意使用第三方库,那么Eclipse Collections具有可用于多种types的zipWithIndex
和forEachWithIndex
。 下面是使用zipWithIndex
针对JDKtypes和Eclipse集合types挑战的一组解决scheme。
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);
这是一个使用forEachWithIndex
的解决scheme。
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);
如果将lambda更改为上面的匿名内部类,那么所有这些代码示例也都可以在Java 5 – 7中使用。
注意:我是Eclipse集合的提交者
这里是AbacusUtil的代码
Stream.of(names).indexed() .filter(e -> e.value().length() <= e.index()) .map(Indexed::value).toList();
披露:我是AbacusUtil的开发者。
如果您碰巧使用了Vavr(以前称为Javaslang),则可以使用专用的方法:
Stream.of("A", "B", "C") .zipWithIndex();
如果我们打印出内容,我们会看到一些有趣的内容:
Stream((A, 0), ?)
这是因为Streams
是懒惰的,我们不知道stream中的下一个项目。
当stream创build列表或数组时,我在这里find了解决scheme(你知道这个大小)。 但是,如果Stream的大小不确定呢? 在这种情况下请尝试以下变体:
public class WithIndex<T> { private int index; private T value; WithIndex(int index, T value) { this.index = index; this.value = value; } public int index() { return index; } public T value() { return value; } @Override public String toString() { return value + "(" + index + ")"; } public static <T> Function<T, WithIndex<T>> indexed() { return new Function<T, WithIndex<T>>() { int index = 0; @Override public WithIndex<T> apply(T t) { return new WithIndex<>(index++, t); } }; } }
用法:
public static void main(String[] args) { Stream<String> stream = Stream.of("a", "b", "c", "d", "e"); stream.map(WithIndex.indexed()).forEachOrdered(e -> { System.out.println(e.index() + " -> " + e.value()); }); }
你可以创build一个静态的内部类来封装索引器,就像我在下面的例子中需要做的那样:
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(); }
这个问题( Stream方法来获得第一个匹配布尔值的元素的索引 )已经将当前的问题标记为重复的,所以我不能在那里回答。 我在这里回答。
这是一个通用的解决scheme来获得不需要外部库的匹配索引。
如果你有一个列表。
public static <T> int indexOf(List<T> items, Predicate<T> matches) { return IntStream.range(0, items.size()) .filter(index -> matches.test(items.get(index))) .findFirst().orElse(-1); }
并像这样调用它:
int index = indexOf(myList, item->item.getId()==100);
如果使用一个集合,试试这个。
public static <T> int indexOf(Collection<T> items, Predicate<T> matches) { int index = -1; Iterator<T> it = items.iterator(); while (it.hasNext()) { index++; if (matches.test(it.next())) { return index; } } return -1; }
一种可能的方法是索引stream中的每个元素:
AtomicInteger index = new AtomicInteger(); Stream.of(names) .map(e->new Object() { String n=e; public i=index.getAndIncrement(); }) .filter(o->onlength()<=oi) // or do whatever you want with pairs... .forEach(o->System.out.println("idx:"+o.i+" nam:"+on));
在stream中使用匿名类不是很好用,而且非常有用。