11

3 years ago a similar question was asked here: Is there a Java equivalent of Python's 'enumerate' function?

I really appreciate the listIterator() solution. Still, I work a lot with the new streams and lambdas (introduced in JDK 8) nowadays and wonder: is there an elegant way of obtaining the index of the element being currently processed? Mine is presented below, but I do not find it especially appealing.

IntStream.range(0, myList.size())
         .mapToObj(i -> doSthWith(myList.get(i), i));
Community
  • 1
  • 1
Radosław Łazarz
  • 950
  • 1
  • 11
  • 25
  • 1
    Though it's less commonly used, `enumerate` works with any iterable (you just have to find a use for the index, since there's no longer a sequence it corresponds to). I suspect a Java solution can do the same with no problem, though it'll be complicated enough to extract in a class of its own and re-used (rather than in-lined as in your example). –  May 22 '14 at 22:16
  • Also it depends on what concrete implementation of list you are using, but calling get on a LinkedList is O(n). So not sure if this solution is worth too. – Alexis C. May 22 '14 at 22:19
  • Right. And that's why I came to SO hoping for an "oh, so that's the way you do it" solution from somebody with greater knowledge. I obviously do not want to recommend the method above to anybody. ;] – Radosław Łazarz May 22 '14 at 22:25
  • 2
    @RadosławŁazarz One workaround would be to do `final AtomicInteger atom= new AtomicInteger(); list.stream().forEach(i -> System.out.println(i+"-"+atom.getAndIncrement()));` – Alexis C. May 22 '14 at 22:28
  • Any cleaner solutions using 3rd party library like apache collections/google Guava ? – balki Jul 28 '17 at 00:00

5 Answers5

18

This question has been asked a few ways before. The key observation is that, unless you have perfect size and splitting information (basically, if your source is an array), then this would be a sequential-only operation.

The "unappealing" answer you propose:

IntStream.range(0, myList.size())
         .mapToObj(i -> doSthWith(myList.get(i), i));

is actually quite efficient when myList is an ArrayList or other list with a fast O(1) indexed-get operation, and parallelizes cleanly. So I think there's nothing wrong with it.

Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
6

There can be some workarounds to this.

You can use an AtomicInteger that you will increment each time you go through an element of the stream

final AtomicInteger atom = new AtomicInteger(); 
list.stream().forEach(i -> System.out.println(i+"-"+atom.getAndIncrement()));

Or using an iterator from another stream for the indexes (a bit like your original idea) but more efficient as you don't call get on the list.

final Iterator<Integer> a = IntStream.range(0, list.size()).iterator();
list.stream().forEach(i -> System.out.println(i+"-"+a.next()));

Well I'm not sure is there exist other nicer alternatives to this (certainly) but that's what I think for the moment.

Note that it's assuming that you are not using a parallel stream. In the latter case that would not be possible to do it like that to obtain a mapping of the elements with their original indexes in the list.

Alexis C.
  • 91,686
  • 21
  • 171
  • 177
  • 2
    In fact, with a parallel stream, you'd (silently) just get wrong answers. Bad idea! – Brian Goetz May 23 '14 at 16:48
  • There is no need to use `list.stream().forEach(…)`. Not only `list.forEach(…)` is simpler, it’s also guaranteed to respect the element order whereas `list.stream().forEach(…)` is not (unlike `list.stream().forEachOrdered(…)` – Holger Jan 15 '16 at 09:59
4

If you want a solution that works with non-RandomAccess lists as well, you can write yourself a simple utility method:

public static <T> void forEach(List<T> list, BiConsumer<Integer,? super T> c) {
    Objects.requireNonNull(c);
    if(list.isEmpty()) return;
    if(list instanceof RandomAccess)
        for(int i=0, num=list.size(); i<num; i++)
            c.accept(i, list.get(i));
    else
        for(ListIterator<T> it=list.listIterator(); it.hasNext(); ) {
            c.accept(it.nextIndex(), it.next());
    }
}

Then you can use it like: forEach(list, (i,s)->System.out.println(i+"\t"+s));


If you swap the order of element and index, you can use an ObjIntConsumer instead of BiConsumer<Integer,? super T> to avoid potential boxing overhead:

public static <T> void forEach(List<T> list, ObjIntConsumer<? super T> c) {
    Objects.requireNonNull(c);
    if(list.isEmpty()) return;
    if(list instanceof RandomAccess)
        for(int i=0, num=list.size(); i<num; i++)
            c.accept(list.get(i), i);
    else
        for(ListIterator<T> it=list.listIterator(); it.hasNext(); ) {
            c.accept(it.next(), it.previousIndex());
    }
}

Then, to be used like forEach(list, (s,i) -> System.out.println(i+"\t"+s));

Holger
  • 285,553
  • 42
  • 434
  • 765
2

The python example in the link you provided is:

>>> numbers = ["zero", "one", "two"]
>>> for i, s in enumerate(numbers):
...     print i, s
... 
0 zero
1 one
2 two

There are several ways to achieve the same output with Java 8, for example:

String[] numbers = {"zero", "one", "two"};
IntStream.range(0, numbers.length)
        .mapToObj(i -> i + " " + numbers[i])
        .forEach(System.out::println);

which outputs:

0 zero
1 one
2 two
assylias
  • 321,522
  • 82
  • 660
  • 783
2

If you want cleaner syntax, then just wrap up some of your calls:

public class IndexedValue<T> {
    public final int index;
    public final T value;
    public IndexedValue(int index, T value) {
        this.index = index;
        this.value = value;
    }
}

public class Itertools {
    public static <T> Stream<IndexedValue<T>> enumerate(ArrayList<T> lst) {
        return IntStream.range(0, lst.size())
            .mapToObj(i -> new IndexedValue<T>(i, lst.get(i)));
    }
}

Then using it:

import static com.example.Itertools.enumerate;

enumerate(myList).map(i -> doSthWith(i.value, i.index));

If you want an efficient LinkedList solution, then you'll need to handle that without making calls to LinkedList.get().

Dane White
  • 3,443
  • 18
  • 16