Add a `find()` builtin function

This idea is similar to this one Adding the method find() to list, but I think it shouldn’t be limited to lists. Also, it can be more generic than “find first occurrence of x”, I often write small function just to loop through an iterable looking for a match with a more complex test.

Suggested implementations:

def find1(_iterable):
  """This one is inspired from built-ins `any` and `all`
  Edit: similar in behavior to 
  `_iterable.index(True)`
  """
  index = 0
  for test in _iterable:
    if test:
      return index
    index += 1
  return None

def find2(predicate, _iterable):
  """This one is inspired from `map` and `filter`.
  Note that in this version, the element could be 
  returned directly instead of returning the index.
  """
  for element in _iterable:
    if predicate(element):
      return element
  return None

Usage examples:

burger = ['bread', 'salad', 'steak', 'bread']

# find1
_example1 = find1(x is 'salad' for x in burger)
_example2 = find1(x.endswith('ad') for x in burger)

# find2
_example3 = find2(lambda x: x is 'salad', burger)
_example4 = find2(lambda x: x.endswith('ad'), burger)
_example5 = find2(isupper, burger)

I stopped counting the number of times I had to create a small function based on this model. It would be nice to have it as built-in.

Since the sequence must be stable for the index to be useful, why do you think a built-in function is more useful than list(iterable).index()?

Personally, I have not run into needing this often enough to feel the need to have be a built-in (I have a need for any() to return the true value it finds more than finding the index of something).

I’m not sold on the index thing. Most often, I only need the element itself, and retrieving it through the index feels inefficient. Should I have this option for the day I’ll need it? I don’t know.

And yes, my need comes from the fact that any does not return the element matched. The function signature is not suited for that though, and it could deserve an update to look more like filter. If this change happens, I’d consider this post obsolete.

Also, you made me realize that for lists I can do that

i = [x.endswith('ad') for x in burger].index(True)
e = burger[i]

But you have to admit the ugliness of this.

Doesn’t next(x for x in burger if x.endswith('ad')) do what you want in an efficient and elegant way?

2 Likes

That’s my usual solution, however the exception it raises when not found is StopIteration with no message, which is unhelpful, so I always end up writing a small helper function to catch it and raise (a subclass of) LookupError

That’s smart. Thanks for sharing! I like it.