94

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
  • 3,623
  • 2
  • 16
  • 39
  • 4
    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

8 Answers8

127

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,912
  • 2
  • 38
  • 50
  • 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
  • 6
    @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
27

Try This:

IntStream.range(0, users.size())
    .filter(userInd-> users.get(userInd).getName().equals(username))
    .findFirst()
    .getAsInt();
Pyves
  • 6,333
  • 7
  • 41
  • 59
AmanSinghal
  • 2,404
  • 21
  • 22
13

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

karmakaze
  • 34,689
  • 1
  • 30
  • 32
  • 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,826
  • 2
  • 32
  • 53
6

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
  • 61
  • 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,458
  • 2
  • 36
  • 44
0

Find If present then Return result based on that.

  1. Find out if element is present or not.
  2. If present return index else return -1
    AtomicInteger index = new AtomicInteger();
    int result =  Arrays.stream(nums)
                         .peek((num) -> index.incrementAndGet())
                         .filter(num -> num == target)
                         .findFirst()
                         .orElse(-1);
    return result == -1 ? result : index.get()-1;
AtomicInteger index = new AtomicInteger();

Is starting from 1 so we want to get index so we are subtracting 1 from index

More Shorter

return Arrays.stream(nums)
                     .peek((num) -> index.incrementAndGet())
                     .filter(num -> num == target)
                     .findFirst()
                     .orElse(-1) == -1 ? -1 : index.get()-1;
Shivam
  • 1
  • 1
  • 1
    So manually re-adding imperative index incrementing is IMO a bad mix of paradigms - at that point you would be better off falling back to an old school for loop. The goal here was to do this in a functional way, in line with the (at the time new(er)) monadic constructs added with Java 8. I'm surprised zipWithIndex still hasn't been introduced, as that's what I'd use in any other language. – Edward Peters Feb 04 '23 at 14:13
-1

Ways to all the index Iterate over the index and get all the index

IntStream.range(0, list.size())
                .filter(i -> Objects.equals(list.get(i), target)).forEach(System.out::println);
Procrastinator
  • 2,526
  • 30
  • 27
  • 36