Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Stream indexOf method based on predicate [duplicate]

I just encountered the situation that I needed to know the index (position) of an element inside a list, but only had a predicate expression to identify the element. I had a look for a Stream function like

int index = list.stream().indexOf(e -> "TESTNAME".equals(e.getName()));

but to no avail. Of course, I could write it like this:

int index = list.indexOf(list.stream().filter(e -> "TESTNAME".equals(e.getName()))
    .findFirst().get());

But this would a) iterate over the list twice (in the worst case that the element would be the last one) and b) would fail if no element matches the predicate (where I would prefer a -1 index).

I wrote a utility method for this functionality:

public static <T> int indexOf(List<T> list, Predicate<? super T> predicate) {
    int idx = 0;
    for (Iterator<T> iter = list.iterator(); iter.hasNext(); idx++) {
        if (predicate.test(iter.next())) {
            return idx;
        }
    }

    return -1;
}

But as this seems to be a really trivial algorithm, I would have expected it somewhere in the Java 8 Stream API. Did I just miss it, or is there really no such function? (Bonus question: In case there is no such method, is there a good reason? Is working with the index in functional programming perhaps an anti-pattern?)

like image 521
Florian Albrecht Avatar asked Apr 09 '18 14:04

Florian Albrecht


4 Answers

Your loop is not bad, but you can simplify it:

public static <T> int indexOf(List<T> list, Predicate<? super T> predicate) {
    for(ListIterator<T> iter = list.listIterator(); iter.hasNext(); )
        if(predicate.test(iter.next())) return iter.previousIndex();
    return -1;
}

You can use a stream like

public static <T> int indexOf(List<T> list, Predicate<? super T> predicate) {
    return IntStream.range(0, list.size())
        .filter(ix -> predicate.test(list.get(ix)))
        .findFirst().orElse(-1);
}

but this will become quite inefficient if the list is large and not random access. I’d stay with the loop.


Starting with Java 9, there’s the alternative

public static <T> int indexOf(List<T> list, Predicate<? super T> predicate) {
    long noMatchPrefix = list.stream().takeWhile(predicate.negate()).count();
    return noMatchPrefix == list.size()? -1: (int) noMatchPrefix;
}

which is really expressive regarding the task “count the elements up to the first match”, but is not exactly the same as “get the index of the first matching element”, which shows when there is no match, so we need to replace the result with -1 then.

like image 165
Holger Avatar answered Nov 09 '22 08:11

Holger


I'd say a simple solution would be to use an IntStream:

IntStream.range(0, list.size())
         .filter(i -> Objects.nonNull(list.get(i)))
         .filter(i -> "TESTNAME".equals(list.get(i).getName()))
         .findFirst()
         .orElse(-1);

Filtering out null elements will prevent this from throwing a NullPointerException.

like image 38
Jacob G. Avatar answered Nov 09 '22 08:11

Jacob G.


    IntPredicate isNotNull = i -> Objects.nonNull(list.get(i));
    IntPredicate isEqualsTestName = i -> list.get(i).getName().equals("TESTNAME");

    int index = IntStream.range(0, list.size())
                .filter(isNotNull.and(isEqualsTestName))
                .findFirst()
                .orElse(-1);
like image 1
Sajit Gupta Avatar answered Nov 09 '22 08:11

Sajit Gupta


The reason is that the concept of an "index" only has meaning in an "ordered Collection". A Stream can come from any Collection such as a Set; having an "indexOf" method would essentially mean giving the source Set a get(int index) operation. Not to mention that a Stream can also come from any other source but a collection.

Another reason is that the implementation of an indexOf operation is that it basically requires a stateful Visitor of the Streams element type; stateful because it has to count the members it already has visited. Stateful operations and Streams don't match well.

As for your utility function, I don't see why you'd write it your way and not like this:

static <T> int indexOf(List<T> findIn, Predicate<? super T> pred) {
    for (int i = 0; i < findIn.size(); i++) {
        if (pred.test(findIn.get(i))) {
            return i;
        }
    }
    return -1;
}
like image 1
daniu Avatar answered Nov 09 '22 09:11

daniu