Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Java 8 equivalent of Python enumerate built-in?

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));
like image 942
Radosław Łazarz Avatar asked May 22 '14 22:05

Radosław Łazarz


3 Answers

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.

like image 130
Brian Goetz Avatar answered Oct 20 '22 14:10

Brian Goetz


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.

like image 6
Alexis C. Avatar answered Oct 20 '22 14:10

Alexis C.


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

like image 4
Holger Avatar answered Oct 20 '22 14:10

Holger