是否有一种简洁的方法来迭代Java 8中的索引流?

Gra*_*oss 361 java java-8 java-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());
Run Code Online (Sandbox Code Playgroud)

与那里给出的LINQ示例相比,这似乎相当令人失望

string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
Run Code Online (Sandbox Code Playgroud)

有更简洁的方法吗?

此外,看起来拉链已移动或被移除......

ass*_*ias 399

最简洁的方法是从一系列指数开始:

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());
Run Code Online (Sandbox Code Playgroud)

结果列表仅包含"Erik".


当您习惯于循环时,一种看起来更熟悉的替代方法是使用可变对象维护一个临时计数器,例如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());
Run Code Online (Sandbox Code Playgroud)

请注意,在并行流上使用后一种方法可能会中断,因为项目不会按顺序"按顺序"处理.

  • 使用原子这种方式对并行流是有问题的.首先,元素的*processing*的顺序不一定与元素在初始数组中出现的顺序相同.因此,使用原子分配的"索引"可能与实际的数组索引不匹配.其次,虽然原子是线程安全的,但是在更新原子的多个线程之间可能会遇到争用,从而降低了并行度. (25认同)
  • @DanielDietrich如果您认为它解决了问题,您应该将其作为答案而不是评论发布(代码也会更具可读性!). (4认同)
  • 为了正义起见,"最干净的方式"是从@Stuart的回答中偷走的. (4认同)
  • @DanielDietrich对不起,如果我正确阅读该代码,它将无法正常工作.您不能让并行运行的管道的不同段与顺序运行.当终端操作开始时,只有"并行"或"顺序"的最后一个被尊重. (3认同)
  • 对答案没有任何侮辱的意思:从函数式编程的角度来看,这是一个非常可悲的解决方案。Java 应该能够做得比这更好。我们只需要让流 API 允许两个变量的回调 (3认同)

Stu*_*rks 66

Java 8流API缺少获取流元素索引的功能以及将流压缩在一起的功能.这是不幸的,因为它使某些应用程序(如LINQ挑战)比其他方式更难.

但是,通常有解决方法.通常,这可以通过用整数范围"驱动"流来完成,并利用原始元素通常在数组中或在索引可访问的集合中的事实.例如,挑战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());
Run Code Online (Sandbox Code Playgroud)

如上所述,这利用了数据源(名称数组)可直接索引的事实.如果不是,这种技术将不起作用.

我承认这不符合挑战2的意图.尽管如此,它确实可以合理有效地解决问题.

编辑

我之前的代码示例用于flatMap融合过滤器和映射操作,但这很麻烦并且没有任何优势.我根据Holger的评论更新了示例.

  • 怎么样`IntStream.range(0,names.length).filter(i-> names [i] .length()<= i).mapToObj(i-> names [i])`?没有拳击它确实有用...... (7认同)
  • 最后重新审视这个...我可能使用了`flatMap`,因为它将过滤和映射操作融合到一个操作中,但这确实没有任何优势.我将编辑该示例. (2认同)

num*_*ro6 39

自番石榴21,你可以使用

Streams.mapWithIndex()
Run Code Online (Sandbox Code Playgroud)

示例(来自官方文档):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"),
    (str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")
Run Code Online (Sandbox Code Playgroud)

  • 另外,番石榴公司的人们还没有实现forEachWithIndex(采用使用者而不是功能),但这是一个已分配的问题:https://github.com/google/guava/issues/2913。 (2认同)
  • Guava 问题似乎仍然悬而未决:-( (2认同)

小智 25

我在我的项目中使用了以下解决方案.我认为它比使用可变对象或整数范围更好.

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));
    }
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*ean 13

除了protonpack之外,jOOλ的Seq提供了这个功能(并且通过扩展库构建在它上面就像独眼巨人反应一样,我是这个库的作者).

Seq.seq(Stream.of(names)).zipWithIndex()
                         .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
                         .toList();
Run Code Online (Sandbox Code Playgroud)

Seq还支持Seq.of(名称),并将构建一个JDK Stream.

简单反应的等价物同样如此

 LazyFutureStream.of(names)
                 .zipWithIndex()
                 .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
                 .toList();
Run Code Online (Sandbox Code Playgroud)

简单反应版本更适合异步/并发处理.


Tag*_*eev 12

为了完整起见,这是涉及我的StreamEx库的解决方案:

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
    .filterKeyValue((idx, str) -> str.length() <= idx+1)
    .values().toList();
Run Code Online (Sandbox Code Playgroud)

在这里,我们创建一个EntryStream<Integer, String>扩展Stream<Entry<Integer, String>>并添加一些特定的操作,如filterKeyValuevalues.也使用toList()快捷方式.

  • @SteveOh如果我理解你的问题是正确的,那么是的,你可以写`.forKeyValue((key,value) - > {})`. (2认同)

Grz*_*rek 7

如果您碰巧使用 Vavr(以前称为 Javaslang),您可以利用专用方法:

Stream.of("A", "B", "C")
  .zipWithIndex();
Run Code Online (Sandbox Code Playgroud)

如果我们打印出内容,我们会看到一些有趣的东西:

Stream((A, 0), ?)
Run Code Online (Sandbox Code Playgroud)

这是因为Streams我们很懒,并且我们不知道流中的下一个项目。


ale*_*x.b 7

在创建列表或数组的流(并且知道大小)后,在这里找到了解决方案。但是,如果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);
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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());
    });
}
Run Code Online (Sandbox Code Playgroud)


use*_*739 5

这是abacus-common的代码

\n
Stream.of(names).indexed()\n      .filter(e -> e.value().length() <= e.index())\n      .map(Indexed::value).toList();\n
Run Code Online (Sandbox Code Playgroud)\n

Disclosure\xef\xbc\x9a 我是 abacus-common 的开发者。

\n


V0i*_*t4r 5

有了清单,您可以尝试

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));
        });
Run Code Online (Sandbox Code Playgroud)

输出:

0 => First
1 => Second
2 => Third
3 => Fourth
4 => Fifth
Run Code Online (Sandbox Code Playgroud)

  • 实际上是个好主意,但是 *strings::indexOf* 可能有点贵。我的建议是改用: *.collect(HashMap::new, (h, s) -&gt; h.put(h.size(), s), (h, s) -&gt; {})* 。您可以简单地使用 size() 方法来创建索引。 (3认同)