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

28

530

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

Animalcule answered 31/8, 2013 at 19:31 Comment(8)
What is intRange()? Haven't come accross this method in Java 8 till now.Fingerprint
@RohitJain Probably an IntStream.rangeClosed(x, y).Fabrikoid
As a side comment, challenge 4 looks better (IMO) with List<String> allCities = map.values().stream().flatMap(list -> list.stream()).collect(Collectors.toList());Fabrikoid
Yes, zip was removed, along with experimental two-valued streams variously called BiStream or MapStream. The main problem is that to do this effectively Java really needs a structurally-typed pair (or tuple) type. Lacking one, it's easy to create a generic Pair or Tuple class -- it's been done many times -- but they all erase to the same type.Actress
Oh, another problem with a generic Pair or Tuple class is that it requires all primitives to be boxed.Actress
Annimon stream library works exactly like Java 8 streams on earlier versions and has some nice additional methods including filterIndexed() github.com/aNNiMON/Lightweight-Stream-APISuspensor
Yep, another area where the Java team was like "eh, close enough, ship it!" and we end up with an incomplete API. You would have thought there would be an overload of map(BiFunction<T, Long, R> mapper) which would provide you with the object and the index. But nope.Brammer
Now that we have Sequenced Collections might be nice to extend streams that derive from these collections to provide something similar to Map.Entry with index and value as fields.Bent
F
586

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

Fabrikoid answered 31/8, 2013 at 19:38 Comment(11)
Using atomics this way is problematic with parallel streams. First, the order of processing of elements won't necessarily the same as the order in which elements occur in the initial array. Thus, the "index" assigned using the atomic probably won't match the actual array index. Second, while atomics are thread-safe, you may encounter contention among multiple threads updating the atomic, degrading the amount of parallelism.Actress
I developed a solution similar to the one by @assylias. To circumvent the problematic with parallel stream @StuartMarks mentioned, I first make a given parallel stream sequential, perform the mapping and restore the parallel state. public static <T> Stream<Tuple2<Integer, T>> zipWithIndex(Stream<T> stream) { final AtomicInteger index = new AtomicInteger(); final Function<T, Tuple2<Integer, T>> zipper = e -> Tuples.of(index.getAndIncrement(), e); if (stream.isParallel()) { return stream.sequential().map(zipper).parallel(); } else { return stream.map(zipper); } }Heliport
@DanielDietrich If you think it solves the question you should post it as an answer rather than a comment (and the code will be more readable too!).Fabrikoid
@DanielDietrich Sorry, if I'm reading that code correctly, it won't work. You can't have different segments of a pipeline running in parallel vs sequential. Only the last of parallel or sequential is honored when the terminal operation commences.Actress
@StuartMarks, of course you are right, this will not work. I should have tested it before - untested could can be considered broken.Heliport
For the sake of justice, "the cleanest way" was stolen from @Stuart's answer.Vomit
Thank you! I feel like sinning when I use AtomicInteger!Pence
Without meaning any insult toward the answer: from a functional programming perspective, this is a really sad solution. Java ought to be able to do better than this. All we need is for the streams API to allow a two-variable callbackNicolais
I would try to loop only once, especially for long lists, instead of using .mapToObj, .map, .forEach .... A good old for (int i = 0; ... loop would be much faster, wouldn't it?Spade
I just want to get a sense of how many items are processed, so the AtomicInteger does the job for me , even in parallelLuo
Imo this is contrarian. The whole point of a stream is that it shouldn't need to be a collection and it should be parallelize-ableDetraction
A
99

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.

Actress answered 1/9, 2013 at 23:31 Comment(1)
Stream.of( Array ) will create a stream interface for an array. Effectively making it into Stream.of( names ).filter( n -> n.length() <= 1).collect( Collectors.toList() ); Less unboxing, and less memory allocation; as we're not creating a range stream anymore.Rondelet
S
76

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")
Sarmentum answered 8/3, 2017 at 14:22 Comment(2)
Also, the Guava folks haven't yet implemented forEachWithIndex (taking a consumer rather than a function), but it's an assigned issue: github.com/google/guava/issues/2913 .Reinstate
That Guava issue still appears to be open :-(Player
I
29

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));
    }
}
Interrogatory answered 14/4, 2014 at 2:58 Comment(1)
+1 -- was able to implement a function that "inserts" an element every N indexes using StreamSupport.stream() and a custom iterator.Prude
A
15

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.

Audrit answered 29/3, 2015 at 20:25 Comment(0)
C
14

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.

Carthy answered 10/9, 2015 at 2:7 Comment(2)
great work; is there a shortcut for .forEach(entry -> {}) ?Darnel
@SteveOh if I understand you question correctly, then yes, you can write .forKeyValue((key, value) -> {}).Carthy
C
12

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());
    });
}
Cromagnon answered 31/8, 2017 at 8:1 Comment(0)
V
9

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
Villalpando answered 6/3, 2017 at 2:46 Comment(2)
Actually a nice idea, but strings::indexOf might be a bit expensive. My suggestion is to use instead: .collect(HashMap::new, (h, s) -> h.put(h.size(), s), (h, s) -> {}) . You can simply use the size() method to create the index.Kiwanis
@Kiwanis Thank you for the suggestion. I'll make the edits.Villalpando
L
7

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.

Larner answered 25/7, 2017 at 17:23 Comment(0)
A
5

Here is code by abacus-common

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

Disclosure: I'm the developer of abacus-common.

Actinia answered 2/12, 2016 at 8:6 Comment(0)
S
3

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);
Sudoriferous answered 16/1, 2015 at 17:33 Comment(0)
A
3

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

Aspiration answered 28/2, 2016 at 6:36 Comment(0)
P
3

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());
Panada answered 5/6, 2019 at 20:57 Comment(0)
C
2

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.

Chidester answered 31/8, 2013 at 19:37 Comment(2)
This is available in other languages/tools. It is simply an incrementing value passed to the map functionWimer
Your link to the documentation is broken.Stalemate
M
2

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.

Milson answered 4/4, 2018 at 19:59 Comment(1)
I really like this. I used it for dealing with 2 collections/arrays that linked with each other via index: IntStream.range(0, list.size()).forEach(i -> list.get(i).setResult(resultArray[i]));Antiicer
G
2

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());
Garett answered 26/10, 2019 at 14:48 Comment(4)
it should be noted, that, if you use this workaround, you should know what you are doing and avoid using idx anywhere else to avoid unexpected behaviour.Captious
@Captious Increasing of idx is fairly straight forward and there is no need to access idx in any additional way. The goal was a less clunky solution.Garett
Why not use AtomicInteger over an array?Cons
@Cons I wanted to show the solution that most closely resembles LINQ. Improvements, such as the wrapping in a class or the use of an attomic integer - also a good idea - would have obscured this. I assume that the OP is able to make these improvements himself.Garett
M
2

Here is a solution that uses a custom gatherer, using the JEP 461: Stream Gatherers Java 22 preview language feature:

void main() {
    Stream<String> stream = Stream.of("A", "B", "C", "D", "E");

    // [[A: 0], [B: 1], [C: 2], [D: 3], [E: 4]]
    System.out.println(stream.gather(withIndex()).toList());
}

static <T> Gatherer<T, ?, IndexedElement<T>> withIndex() {
    class Index {
        int index = 0;
    }

    return Gatherer.ofSequential(
            Index::new,
            Gatherer.Integrator.ofGreedy((state, element, downstream) ->
                    downstream.push(new IndexedElement<>(element, state.index++)))
    );

record IndexedElement<E>(E value, int index) {
    @Override
    public String toString() {
        return "[%s: %s]".formatted(value, index);
    }
}

This custom gatherer keeps track of the current index, and maps each element to a custom IndexedElement type containing the element and the index.

Javadocs

Gatherer:

An intermediate operation that transforms a stream of input elements into a stream of output elements, optionally applying a final action when the end of the upstream is reached. […]

[…]

There are many examples of gathering operations, including but not limited to: grouping elements into batches (windowing functions); de-duplicating consecutively similar elements; incremental accumulation functions (prefix scan); incremental reordering functions, etc. The class Gatherers provides implementations of common gathering operations.

API Note:

A Gatherer is specified by four functions that work together to process input elements, optionally using intermediate state, and optionally perform a final action at the end of input. They are:

Stream.gather(Gatherer<? super T,?,R> gatherer):

Returns a stream consisting of the results of applying the given gatherer to the elements of this stream.

Gatherer.ofSequential(initializer, integrator)

Returns a new, sequential, Gatherer described by the given initializer and integrator.

Malaspina answered 3/1 at 23:7 Comment(0)
L
1

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.

Lanfranc answered 31/8, 2017 at 21:22 Comment(0)
I
1

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()));
             });
}
Incest answered 11/5, 2020 at 21:13 Comment(0)
B
0

This question (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;
    }
Basile answered 21/5, 2017 at 13:18 Comment(0)
S
0
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);
Subbasement answered 29/8, 2019 at 3:21 Comment(2)
The solution of the above question is expected to be a List containing one element Erik.Garett
I have added an example to collect in the List as well.Subbasement
P
0

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.

Portemonnaie answered 22/4, 2020 at 19:37 Comment(0)
M
0

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);
Morning answered 28/10, 2021 at 9:14 Comment(0)
R
0

If the list is unique, we can make use of indexOf method.

List<String> names = Arrays.asList("Sam", "Pamela", "Dave", "Pascal", "Erik");

    names.forEach(name ->{
        System.out.println((names.indexOf(name) + 1) + ": " + name);
    });
Redeploy answered 3/8, 2022 at 6:22 Comment(1)
Not recommended. This has runtime O(N^2)Cons
N
0

Is there a concise way to iterate over a stream whilst having access to the index in the stream?

If you think of iterate over as a terminal operation (like forEach), then yes, there is a simple way to access index:

Stream<String> names = Stream.of("Sam","Pamela", "Dave", "Pascal", "Erik");
List<String> filtered = new ArrayList<>();

names.reduce(0L, (index, item) -> {
    log.info("Iteration over items [index: {}, item: {}]", index, item);
    if (item.length() < index) {
        filtered.add(item);
    }
    return index + 1;
}, Long::sum);

This works for sequential streams only.

Parallel streams must be first transformed into sequential ones:

names.sequential().reduce(0L, (index, item) -> {
    log.info("Iteration over items [index: {}, item: {}]", index, item);
    if (item.length() < index) {
        filtered.add(item);
    }
    return index + 1;
}, Long::sum);
Northey answered 19/4, 2023 at 15:26 Comment(0)
A
0
ArrayList result = new ArrayList()
for(int i = 0; i < names.length(); i++){
  if(names[i].length() < i+1) {
    result.add(names[i])
  }
}
return result;

For god sake, follow KISS principle (Keep it simple s...).

The most performant, readable and predictable way to write it. Just put inside a method and be happy without bugs and unprectictability.

Abdias answered 14/7, 2023 at 14:41 Comment(0)
B
0

Without a library dependency you can write such an iterator

public <T> void iterate(List<T> l, BiConsumer<Integer, T> f) {
    for (int i = 0; i < l.size(); i++) f.accept(i, l.get(i));
}

Used like this

iterate(asList("1","2","3"), (i, s) -> out.println(i + ": " + s));
Bauer answered 21/4 at 3:32 Comment(0)
L
-1

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();
}
Lamont answered 14/2, 2016 at 2:14 Comment(1)
'Static inner" is a contradiction in terms. Indexer is a static class.Heterocyclic

© 2022 - 2024 — McMap. All rights reserved.