5

Is there a built-in python equivalent of std::find_if to find the first element of a list for which a given condition is true? In other words, something like the index() function of a list, but with an arbitrary unary predicate rather than just a test for equality.

I don't want to use list comprehension, because the specific predicate I have in mind is somewhat expensive to compute.

taras
  • 5,922
  • 10
  • 36
  • 48
Mike Hawk
  • 341
  • 1
  • 4
  • 20
  • Are you looking for the 'in' command?: if x in list: do something. – Rob Jul 18 '18 at 21:01
  • Did you read this answer? https://stackoverflow.com/a/9542768/10077 Look at "Finding the first occurrence". – Fred Larson Jul 18 '18 at 21:02
  • Seems like you want [assignment expressions](https://www.python.org/dev/peps/pep-0572/)... *"it allows us to conveniently capture a "witness" for an `any()` expression"*. – jonrsharpe Jul 18 '18 at 21:05

2 Answers2

9

Using a tip from an answer to a related question, and borrowing from the answer taras posted, I came up with this:

>>> lst=[1,2,10,3,5,3,4]
>>> next(n for n in lst if n%5==0)
10

A slight modification will give you the index rather than the value:

>>> next(idx for idx,n in enumerate(lst) if n%5==0)
2

Now, if there was no match this will raise an exception StopIteration. You might want use a function that handles the exception and returns None if there was no match:

def first_match(iterable, predicate):
    try:
        return next(idx for idx,n in enumerate(iterable) if predicate(n))
    except StopIteration:
        return None

lst=[1,2,10,3,5,3,4]
print(first_match(lst, lambda x: x%5 == 0))

Note that this uses a generator expression, not a list comprehension. A list comprehension would apply the condition to every member of the list and produce a list of all matches. This applies it to each member until it finds a match and then stops, which is the minimum work to solve the problem.

Fred Larson
  • 58,972
  • 15
  • 110
  • 164
1

Say, you have some predicate function pred and a list lst. You can use itertools.dropwhile to get the first element in lst, for which pred returns True with

itertools.dropwhile(lambda x: not pred(x), lst).next()

It skips all elements for which pred(x) is False and .next() yields you the value for which pred(x) is True.

Edit:

A sample use to find the first element in lst divisible by 5

>>> import itertools
>>> lst = [1,2,10,3,5,3,4]
>>> pred = lambda x: x % 5 == 0 
>>> itertools.dropwhile(lambda x: not pred(x), lst).next()
10
taras
  • 5,922
  • 10
  • 36
  • 48
  • Using the answer I linked in comments above, I was also able to accomplish this with just `next(n for n in lst if n%5==0)`. – Fred Larson Jul 18 '18 at 21:15
  • Yes, definitely, but OP wishes not to use a list comprehension. – taras Jul 18 '18 at 21:17
  • 1
    There's no list comprehension there. Only a generator expression. – Fred Larson Jul 18 '18 at 21:18
  • Well, I am completely fine with your approach, yet they do share the same syntax construct. – taras Jul 18 '18 at 21:21
  • Thank you for this good answer; I accepted the other one because it is "pure" python and doesn't require any imports. – Mike Hawk Jul 19 '18 at 13:13
  • @MikeHawk, I find the accepted answer more plausible myself. In fact my approach does the same "under the hood" (if one updated it to return the index instead of the value) – taras Jul 19 '18 at 13:15