【问题标题】:Is there a concise way to iterate over a stream with indices in Java 8?在 Java 8 中是否有一种简洁的方法来迭代带有索引的流?
【发布时间】:2017-03-27 12:44:19
【问题描述】:

是否有一种简洁的方法可以在访问流中的索引的同时迭代流?

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();

有没有更简洁的方法?

此外,似乎 zip 已移动或已被删除...

【问题讨论】:

  • 什么是intRange()?直到现在还没有在 Java 8 中遇到过这种方法。
  • @RohitJain 可能是IntStream.rangeClosed(x, y)
  • 作为旁注,List&lt;String&gt; allCities = map.values().stream().flatMap(list -&gt; list.stream()).collect(Collectors.toList()); 的挑战 4 看起来更好(IMO)
  • 是的,zip 已被删除,还有实验性的二值流(称为 BiStreamMapStream)。主要问题是,为了有效地做到这一点,Java 确实需要一个结构类型的对(或元组)类型。缺少一个,创建一个通用的 Pair 或 Tuple 类很容易——已经做过很多次了——但它们都擦除为相同的类型。
  • 哦,泛型 Pair 或 Tuple 类的另一个问题是它需要对所有原语进行装箱。

标签: java java-8 java-stream


【解决方案1】:

没有办法在访问索引的同时迭代Stream,因为Stream 不同于任何CollectionStream 只是将数据从一个地方传送到另一个地方的管道,如documentation 中所述:

没有存储空间。流不是存储元素的数据结构;相反,它们通过计算操作的管道从源(可能是数据结构、生成器、IO 通道等)携带值。

当然,正如您在问题中暗示的那样,您始终可以将您的 Stream&lt;V&gt; 转换为 Collection&lt;V&gt;,例如 List&lt;V&gt;,您将可以访问其中的索引。

【讨论】:

  • 这在其他语言/工具中可用。它只是传递给 map 函数的递增值
  • 您的文档链接已损坏。
【解决方案2】:

最简洁的方法是从索引流开始:

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”。


当您习惯使用 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());

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

【讨论】:

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

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());

正如我上面提到的,这利用了数据源(名称数组)可直接索引的事实。否则,这种技术就行不通。

我承认这并不能满足挑战 2 的意图。尽管如此,它确实合理有效地解决了问题。

编辑

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

【讨论】:

  • IntStream.range(0, names.length).filter(i-&gt;names[i].length()&lt;=i).mapToObj(i-&gt;names[i]) 怎么样?不用拳击它也能工作……
  • 嗯,是的,为什么我认为我需要使用flatMap
  • 终于重新审视这个......我可能使用了flatMap,因为它将过滤和映射操作融合到一个操作中,但这并没有提供任何优势。我将编辑示例。
  • Stream.of( Array ) 将为数组创建流接口。有效地把它变成Stream.of( names ).filter( n -&gt; n.length() &lt;= 1).collect( Collectors.toList() ); 更少的拆箱,更少的内存分配;因为我们不再创建范围流。
【解决方案4】:

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

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));
    }
}

【讨论】:

  • +1 -- 能够实现一个函数,使用StreamSupport.stream() 和自定义迭代器,每 N 个索引“插入”一个元素。
【解决方案5】:

https://github.com/poetix/protonpack 你可以做那个拉链:

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);

【讨论】:

    【解决方案6】:

    除了 protonpack,jOOλ's Seq 还提供了这个功能(以及像 cyclops-react 这样的扩展库,我是这个库的作者)。

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

    Seq 也仅支持 Seq.of(names) 并将在幕后构建一个 JDK Stream。

    simple-react 等价物看起来类似

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

    simple-react 版本更适合异步/并发处理。

    【讨论】:

      【解决方案7】:

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

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

      在这里,我们创建了一个 EntryStream&lt;Integer, String&gt;,它扩展了 Stream&lt;Entry&lt;Integer, String&gt;&gt; 并添加了一些特定的操作,例如 filterKeyValuevalues。还使用了toList() 快捷方式。

      【讨论】:

      • 干得好; .forEach(entry -&gt; {}) 有快捷方式吗?
      • @SteveOh 如果我理解你的问题,那么是的,你可以写.forKeyValue((key, value) -&gt; {})
      【解决方案8】:

      您可以创建一个静态内部类来封装索引器,就像我在下面的示例中需要做的那样:

      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();
      }
      

      【讨论】:

        【解决方案9】:

        如果您不介意使用第三方库,Eclipse CollectionszipWithIndexforEachWithIndex 可用于多种类型。下面是一组使用 zipWithIndex 的 JDK 类型和 Eclipse Collections 类型的挑战的解决方案。

        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 的解决方案。

        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 Collections 的提交者

        【讨论】:

          【解决方案10】:

          这是AbacusUtil的代码

          Stream.of(names).indexed()
                .filter(e -> e.value().length() <= e.index())
                .map(Indexed::value).toList();
          

          披露:我是AbacusUtil的开发者。

          【讨论】:

            【解决方案11】:

            有一个列表你可以试试

            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
            

            【讨论】:

            • 其实是个好主意,但是 strings::indexOf 可能有点贵。我的建议是改用: .collect(HashMap::new, (h, s) -> h.put(h.size(), s), (h, s) -> {})我> 。您可以简单地使用 size() 方法来创建索引。
            • @gil.fernandes 感谢您的建议。我会进行修改。
            【解决方案12】:

            从番石榴21开始,可以使用

            Streams.mapWithIndex()
            

            示例(来自official doc):

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

            【讨论】:

            • 此外,Guava 还没有实现 forEachWithIndex(采用消费者而不是函数),但这是一个已分配的问题:github.com/google/guava/issues/2913
            • Guava 问题似乎仍然是开放的 :-(
            【解决方案13】:

            这个问题(Stream Way to get index of first element matching boolean)已将当前问题标记为重复,所以我无法在那里回答;我在这里回答。

            这里是获取匹配索引的通用解决方案,不需要外部库。

            如果你有一个清单。

            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;
                }
            

            【讨论】:

              【解决方案14】:

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

              Stream.of("A", "B", "C")
                .zipWithIndex();
              

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

              Stream((A, 0), ?)
              

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

              【讨论】:

                【解决方案15】:

                当 Stream 由列表或数组创建时(并且您知道大小),我在这里找到了解决方案。但是如果 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());
                    });
                }
                

                【讨论】:

                  【解决方案16】:

                  一种可能的方法是对流中的每个元素进行索引:

                  AtomicInteger index = new AtomicInteger();
                  Stream.of(names)
                    .map(e->new Object() { String n=e; public i=index.getAndIncrement(); })
                    .filter(o->o.n.length()<=o.i) // or do whatever you want with pairs...
                    .forEach(o->System.out.println("idx:"+o.i+" nam:"+o.n));
                  

                  在流中使用匿名类并没有很好地使用,但非常有用。

                  【讨论】:

                    【解决方案17】:

                    如果您尝试基于谓词获取索引,请尝试以下操作:

                    如果你只关心第一个索引:

                    OptionalInt index = IntStream.range(0, list.size())
                        .filter(i -> list.get(i) == 3)
                        .findFirst();
                    

                    或者如果你想查找多个索引:

                    IntStream.range(0, list.size())
                       .filter(i -> list.get(i) == 3)
                       .collect(Collectors.toList());
                    

                    添加.orElse(-1); 以防万一您想在找不到值时返回它。

                    【讨论】:

                    • 我真的很喜欢这个。我用它来处理通过索引相互链接的 2 个集合/数组:IntStream.range(0, list.size()).forEach(i -&gt; list.get(i).setResult(resultArray[i]));
                    【解决方案18】:

                    可以使用IntStream.iterate()获取索引:

                    String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
                    List<String> nameList = IntStream.iterate(0, i -> i < names.length, i -> i + 1)
                            .filter(i -> names[i].length() <= i)
                            .mapToObj(i -> names[i])
                            .collect(Collectors.toList());
                    

                    这仅适用于 Java 9 以上,在 Java 8 中你可以使用这个:

                    String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
                    List<String> nameList = IntStream.iterate(0, i -> i + 1)
                            .limit(names.length)
                            .filter(i -> names[i].length() <= i)
                            .mapToObj(i -> names[i])
                            .collect(Collectors.toList());
                    

                    【讨论】:

                      【解决方案19】:
                      String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
                      String completeString
                               =  IntStream.range(0,namesArray.length)
                                 .mapToObj(i -> namesArray[i]) // Converting each array element into Object
                                 .map(String::valueOf) // Converting object to String again
                                 .collect(Collectors.joining(",")); // getting a Concat String of all values
                              System.out.println(completeString);
                      

                      输出:山姆、帕梅拉、戴夫、帕斯卡、埃里克

                      String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
                      
                      IntStream.range(0,namesArray.length)
                                     .mapToObj(i -> namesArray[i]) // Converting each array element into Object
                                     .map(String::valueOf) // Converting object to String again
                                     .forEach(s -> {
                                      //You can do various operation on each element here
                                      System.out.println(s);
                                     }); // getting a Concat String of all 
                      

                      在列表中收集:

                      String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
                       List<String> namesList
                                      =  IntStream.range(0,namesArray.length)
                                      .mapToObj(i -> namesArray[i]) // Converting each array element into Object
                                      .map(String::valueOf) // Converting object to String again
                                      .collect(Collectors.toList()); // collecting elements in List
                              System.out.println(listWithIndex);
                      

                      【讨论】:

                      • 上述问题的解决方案预计是List包含一个元素Erik
                      • 我也添加了一个示例来收集到列表中。
                      【解决方案20】:

                      你不需要 map 必须
                      这是最接近 LINQ 示例的 lambda:

                      int[] idx = new int[] { 0 };
                      Stream.of(names)
                          .filter(name -> name.length() <= idx[0]++)
                          .collect(Collectors.toList());
                      

                      【讨论】:

                        【解决方案21】:

                        正如 jean-baptiste-yunès 所说,如果您的流基于 java List,那么使用 AtomicInteger 及其 incrementAndGet 方法是解决问题的一个很好的方法,并且返回的整数确实对应于原始 List 中的索引只要你不使用并行流。

                        【讨论】:

                          【解决方案22】:

                          如果您需要 forEach 中的索引,那么这提供了一种方法。

                            public class IndexedValue {
                          
                              private final int    index;
                              private final Object value;
                          
                              public IndexedValue(final int index, final Object value) { 
                                  this.index = index;
                                  this.value = value;
                              }
                          
                              public int getIndex() {
                                  return index;
                              }
                          
                              public Object getValue() {
                                  return value;
                              }
                          }
                          

                          然后如下使用它。

                          @Test
                          public void withIndex() {
                              final List<String> list = Arrays.asList("a", "b");
                              IntStream.range(0, list.size())
                                       .mapToObj(index -> new IndexedValue(index, list.get(index)))
                                       .forEach(indexValue -> {
                                           System.out.println(String.format("%d, %s",
                                                                            indexValue.getIndex(),
                                                                            indexValue.getValue().toString()));
                                       });
                          }
                          

                          【讨论】:

                            【解决方案23】:

                            这是标准 Java 的解决方案:

                            在线解决方案:

                            Arrays.stream("zero,one,two,three,four".split(","))
                                    .map(new Function<String, Map.Entry<Integer, String>>() {
                                        int index;
                            
                                        @Override
                                        public Map.Entry<Integer, String> apply(String s) {
                                            return Map.entry(index++, s);
                                        }
                                    })
                                    .forEach(System.out::println);
                            

                            以及更易读的实用方法解决方案:

                            static <T> Function<T, Map.Entry<Integer, T>> mapWithIntIndex() {
                                return new Function<T, Map.Entry<Integer, T>>() {
                                    int index;
                            
                                    @Override
                                    public Map.Entry<Integer, T> apply(T t) {
                                        return Map.entry(index++, t);
                                    }
                                };
                            }
                            
                            ...
                            Arrays.stream("zero,one,two,three,four".split(","))
                                    .map(mapWithIntIndex())
                                    .forEach(System.out::println);
                            

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 2011-12-29
                              • 2019-01-27
                              • 2011-11-05
                              • 1970-01-01
                              • 2019-08-24
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多