Is there a concise way to iterate over a stream with indices in Java 8?

JavaJava 8Java Stream

Java Problem Overview


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

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...

Java Solutions


Solution 1 - Java

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.


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:

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".

Solution 2 - Java

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.

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:

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.

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.

Solution 3 - Java

Since guava 21, you can use

Streams.mapWithIndex()

Example (from official doc):

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

Solution 4 - Java

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

Solution 5 - Java

In addition to protonpack, jOOλ's Seq provides this functionality (and by extension libraries that build on it like cyclops-react, I am the author of this library).

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.

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.

Solution 6 - Java

Just for completeness here's the solution involving my StreamEx library:

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.

Solution 7 - Java

I found the solutions here when the Stream is created of list or array (and you know the size). But what if Stream is with unknown size? In this case try this variant:

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

Usage:

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

Solution 8 - Java

With a List you can try

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

Output:

0 => First
1 => Second
2 => Third
3 => Fourth
4 => Fifth

Solution 9 - Java

If you happen to use Vavr(formerly known as Javaslang), you can leverage the dedicated method:

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

If we print out the content, we will see something interesting:

Stream((A, 0), ?)

This is because Streams are lazy and we have no clue about next items in the stream.

Solution 10 - Java

Here is code by AbacusUtil

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

Disclosure: I'm the developer of AbacusUtil.

Solution 11 - Java

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:

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.

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.

Solution 12 - Java

With https://github.com/poetix/protonpack u can do that 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);

Solution 13 - Java

If you don't mind using a third-party library, Eclipse Collections has 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.

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.

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.

Note: I am a committer for Eclipse Collections

Solution 14 - Java

If you are trying to get an index based on a predicate, try this:

If you only care about the first index:

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

Or if you want to find multiple indexes:

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

Add .orElse(-1); in case you want to return a value if it doesn't find it.

Solution 15 - Java

You can use IntStream.iterate() to get the index:

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

This only works for Java 9 upwards in Java 8 you can use this:

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

Solution 16 - Java

One possible way is to index each element on the flow:

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

Using an anonymous class along a stream is not well-used while being very useful.

Solution 17 - Java

you don't need a map necessarily
that is the closest lambda to the LINQ example:

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

Solution 18 - Java

If you need the index in the forEach then this provides a way.

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

Then use it as follows.

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

Solution 19 - Java

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

Solution 20 - Java

This question (https://stackoverflow.com/questions/38963338/stream-way-to-get-index-of-first-element-matching-boolean) has marked the current question as a duplicate, so I can not answer it there; I am answering it here.

Here is a generic solution to get the matching index that does not require an external library.

If you have a list.

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

And call it like this:

int index = indexOf(myList, item->item.getId()==100);

And if using a collection, try this one.

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

Solution 21 - Java

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

OUTPUT : Sam,Pamela,Dave,Pascal,Erik

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 

To Collect in the List:

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

Solution 22 - Java

As jean-baptiste-yunès said, if your stream is based on a java List then using an AtomicInteger and its incrementAndGet method is a very good solution to the problem and the returned integer does correspond to the index in the original List as long as you do not use a parallel stream.

Solution 23 - Java

Here's solution for standard Java:

In-line solution:

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

and more readable solution with utility method:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGraeme MossView Question on Stackoverflow
Solution 1 - JavaassyliasView Answer on Stackoverflow
Solution 2 - JavaStuart MarksView Answer on Stackoverflow
Solution 3 - Javanuméro6View Answer on Stackoverflow
Solution 4 - Javauser1195526View Answer on Stackoverflow
Solution 5 - JavaJohn McCleanView Answer on Stackoverflow
Solution 6 - JavaTagir ValeevView Answer on Stackoverflow
Solution 7 - Javaalex.bView Answer on Stackoverflow
Solution 8 - JavaV0idst4rView Answer on Stackoverflow
Solution 9 - JavaGrzegorz PiwowarekView Answer on Stackoverflow
Solution 10 - Javauser_3380739View Answer on Stackoverflow
Solution 11 - JavaJosh MView Answer on Stackoverflow
Solution 12 - Java42n4View Answer on Stackoverflow
Solution 13 - JavaDonald RaabView Answer on Stackoverflow
Solution 14 - Javalive-loveView Answer on Stackoverflow
Solution 15 - JavaSamuel PhilippView Answer on Stackoverflow
Solution 16 - JavaJean-Baptiste YunèsView Answer on Stackoverflow
Solution 17 - JavaKaplanView Answer on Stackoverflow
Solution 18 - JavaB. StackhouseView Answer on Stackoverflow
Solution 19 - JavaalexpfxView Answer on Stackoverflow
Solution 20 - JavaSteven SpunginView Answer on Stackoverflow
Solution 21 - JavaArpan SainiView Answer on Stackoverflow
Solution 22 - JavamarioView Answer on Stackoverflow
Solution 23 - JavavbezhenarView Answer on Stackoverflow