76

I have a List<Users>. I want to get the index of the (first) user in the stream with a particular username. I don't want to actually require the User to be .equals() to some described User, just to have the same username.

I can think of ugly ways to do this (iterate and count), but it feels like there should be a nice way to do this, probably by using Streams. So far the best I have is:

int index = users.stream()
    .map(user -> user.getName())
    .collect(Collectors.toList())
    .indexOf(username);

Which isn't the worst code I've ever written, but it's not great. It's also not that flexible, as it relies on there being a mapping function to a type with a .equals() function that describes the property you're looking for; I'd much rather have something that could work for arbitrary Function<T, Boolean>

Anyone know how?

Edward Peters
  • 2,433
  • 1
  • 12
  • 26
  • 3
    Why is "iterate" *ugly*? – Andreas Aug 15 '16 at 21:45
  • 2
    Streams and indexing don't mix well. You're usually better off falling back to an old-style loop at that point. – Louis Wasserman Aug 15 '16 at 21:46
  • 1
    @Andreas The thing I like about streams is the separation of the collection-related logic from the specific thing being asked. In this case, there are a ton of different questions that could be asked that only vary from the core `Function`, so it feels like there should be a way to handle that that abstracts it from the general collection logic. – Edward Peters Aug 15 '16 at 21:55
  • What prevents you from using a `Function` in an `if` statement inside a `for` loop? Why do you want to use `Function` when you have `Predicate`? – Andreas Aug 15 '16 at 21:57
  • 1
    @Andreas Because there you're manually describing all of the structure-related code, rather than having that compartmentalized. To the other question, I just forgot that `Predicate` was a thing. – Edward Peters Aug 15 '16 at 22:09
  • 5
    Neat Java 11 solution: `int index = users.stream().map(User::getName).takeWhile(not(username::equals)).count(); ` – Klitos Kyriacou Nov 09 '18 at 17:11

6 Answers6

106

Occasionally there is no pythonic zipWithIndex in java. So I came across something like that:

OptionalInt indexOpt = IntStream.range(0, users.size())
     .filter(i -> searchName.equals(users.get(i)))
     .findFirst();

Alternatively you can use zipWithIndex from protonpack library

Note

That solution may be time-consuming if users.get is not constant time operation.

vsminkov
  • 10,252
  • 2
  • 36
  • 49
  • That works. I could also skip the pair by just looking up the user with the given index in the filter operation... not sure if that's better or worse. – Edward Peters Aug 15 '16 at 21:38
  • @EdwardPeters It's definitely better. At least that eliminates additional memory traffic – vsminkov Aug 15 '16 at 21:39
  • 5
    @vsminkov, You should be careful here. Nobody said that the list has O(1) access. – SerCe Aug 15 '16 at 21:46
  • 2
    @SerCe fair enough. the other option is to implement custom spliterator like protonpack does. I'll put a note – vsminkov Aug 15 '16 at 21:50
20

Try This:

IntStream.range(0, users.size())
    .filter(userInd-> users.get(userInd).getName().equals(username))
    .findFirst()
    .getAsInt();
Pyves
  • 6,003
  • 7
  • 40
  • 58
AmanSinghal
  • 2,234
  • 19
  • 21
12

Using Guava library: int index = Iterables.indexOf(users, u -> searchName.equals(u.getName()))

karmakaze
  • 32,491
  • 1
  • 29
  • 31
  • 3
    the most readable solution and chances guava - like commons-lang - is already in your POM ;) More elegant than an IntStream IMHO! – user1075613 Nov 28 '20 at 02:42
11

You can try StreamEx library made by Tagir Valeev. That library has a convenient #indexOf method.

This is a simple example:

List<User> users = asList(new User("Vas"), new User("Innokenty"), new User("WAT"));
long index = StreamEx.of(users)
        .indexOf(user -> user.name.equals("Innokenty"))
        .getAsLong();
System.out.println(index);
SerCe
  • 5,528
  • 2
  • 28
  • 50
5

A solution without any external library

AtomicInteger i = new AtomicInteger(); // any mutable integer wrapper
int index = users.stream()
    .peek(v -> i.incrementAndGet())
    .anyMatch(user -> user.getName().equals(username)) ? // your predicate
    i.get() - 1 : -1;

peek increment index i while predicate is false hence when predicate is true i is 1 more than matched predicate => i.get() -1

hibour
  • 51
  • 1
  • 4
  • This a dangerous approach, as streams, depending on the source, can be processed in parallel, in which case the index can be completely off. You should add `.sequential()` before `.peek()` to make this reliable. – Pawel Veselov Dec 15 '21 at 22:58
2

There is the detectIndex method in the Eclipse Collections library which takes a Predicate.

int index = ListIterate.detectIndex(users, user -> username.equals(user.getName()));

If you have a method on User class which returns boolean if username matches you can use the following:

int index = ListIterate.detectIndexWith(users, User::named, username);

Note: I a committer for Eclipse Collections

Donald Raab
  • 6,038
  • 2
  • 33
  • 38