To find the first element in a sequence seq that matches a predicate:
next(x for x in seq if predicate(x))
Or simply:
Python 2:
next(itertools.ifilter(predicate, seq))
Python 3:
next(filter(predicate, seq))
These will raise a StopIteration exception if the predicate does not match for any element.
To return None if there is no such element:
next((x for x in seq if predicate(x)), None)
Or:
next(filter(predicate, seq), None)
Answer from jfs on Stack OverflowTo find the first element in a sequence seq that matches a predicate:
next(x for x in seq if predicate(x))
Or simply:
Python 2:
next(itertools.ifilter(predicate, seq))
Python 3:
next(filter(predicate, seq))
These will raise a StopIteration exception if the predicate does not match for any element.
To return None if there is no such element:
next((x for x in seq if predicate(x)), None)
Or:
next(filter(predicate, seq), None)
You could use a generator expression with a default value and then next it:
next((x for x in seq if predicate(x)), None)
Although for this one-liner you need to be using Python >= 2.6.
This rather popular article further discusses this issue: Cleanest Python find-in-list function?.
Finding the first item in a python list which is big/small enough - Code Review Stack Exchange
How can I find a value in a list using Python? - Ask a Question - TestMu AI (formerly LambdaTest) Community
Check if a value is the first occurence of a list and mark as 1 in Python - Stack Overflow
python - Get the first item from an iterable that matches a condition - Stack Overflow
Good enough: maybe not. Tastes and purposes vary, but I would say no. (1) The current implementation is needlessly specific and repetitive. (2) The defaults are not parameterized and their values are puzzling: the function is supposed to return an index, unless in fails to find a match, in which case it defaults to infinite? (3) The naming convention is awkwardly long and does not scale well should you find yourself needing more functions like this.
Start with a flexible foundation. Because it's easy to do, we can begin with a general utility function that borrows its name from more-itertools.
def locate_first(xs, predicate, default = None):
gen = (i for i, x in enumerate(xs) if predicate(x))
return next(gen, default)
Add convenience helpers as needed. Maybe that function is good enough for your use case. If not, you can create conveniences like this:
def locate_first_gte(xs, threshold, default = None):
pred = lambda x: x >= threshold
return locate_first(xs, pred, default)
1. Returning math.inf as the 'match not found' index seems like a poor API.
I think I understand where the intention is coming from, since -1 would be the most likely candidate value; but is also a valid python index.
You could return None as FMc suggests, or you could simply allow next to raise a StopIteration; yielding responsibility back to the caller to figure out how to handle things.
2. Having repeated code is perfectly fine, in moderation
I disagree with the other answer that claims your code is unnecessarily specific and repetitive. In fact, I would claim that their suggestion, whilst more flexible is in fact a premature abstraction. You have two, simple one-liner functions that do different things, sure they share some repeated logic, but they are simple and easy to understand at a glance.
Compare your version:
def index_of_first_item_less_or_equal_than(lst, threshold):
return next((i for i, x in enumerate(lst) if x <= threshold), math.inf)
def index_of_first_item_greater_or_equal_than(lst, threshold):
return next((i for i, x in enumerate(lst) if x >= threshold), math.inf)
To the equivalent abstracted version suggested by FMc.
def locate_first(xs, predicate, default = None):
gen = (i for i, x in enumerate(xs) if predicate(x))
return next(gen, default)
def locate_first_gte(xs, threshold, default = None):
return locate_first(xs, lambda x: x >= threshold, default)
def locate_first_lte(xs, threshold, default = None):
return locate_first(xs, lambda x: x <= threshold, default)
The abstracted version is necessarily more complicated than your version. There is a place for such abstractions. I would suggest you avoid premature abstractions until you actually need the flexibility they offer, at which point you'll be better armed to choose a suitable abstraction.
Abstracting too early is a surefire way to end up with needlessly complex software.
Conclusion Here's what I think I'd do in your shoes:
from typing import Sequence
def index_of_first_item_lte_than(sequence: Sequence, threshold: float) -> int:
"""Return the index of the first item less than or equal to `threshold`.
:raises StopIteration: if no such item is found.
"""
return next((i for i, x in enumerate(lst) if x <= threshold))
def index_of_first_item_gte_than(sequence: Sequence, threshold: float) -> int:
"""Return the index of the first item greater than or equal to `threshold`.
:raises StopIteration: if no such item is found.
"""
return next((i for i, x in enumerate(lst) if x >= threshold))
You can use itertools.count and collections.defaultdict for the task:
from itertools import count
from collections import defaultdict
my_lst = ['a', 'b', 'c', 'c', 'a', 'd']
d = defaultdict(count)
out = [int(next(d[v])==0) for v in my_lst]
print(out)
Prints:
[1, 1, 1, 0, 0, 1]
If you want a barebones python solution, this monstrosity would work:
[*map(int, map(lambda x, y: x == my_lst.index(y), *zip(*enumerate(my_lst))))]
Out[30]: [1, 1, 1, 0, 0, 1]
For all items in my_lst, it returns 1 if its index is the index of the first occurrence.
Python 2.6+ and Python 3:
If you want StopIteration to be raised if no matching element is found:
next(x for x in the_iterable if x > 3)
If you want default_value (e.g. None) to be returned instead:
next((x for x in the_iterable if x > 3), default_value)
Note that you need an extra pair of parentheses around the generator expression in this case − they are needed whenever the generator expression isn't the only argument.
I see most answers resolutely ignore the next built-in and so I assume that for some mysterious reason they're 100% focused on versions 2.5 and older -- without mentioning the Python-version issue (but then I don't see that mention in the answers that do mention the next built-in, which is why I thought it necessary to provide an answer myself -- at least the "correct version" issue gets on record this way;-).
Python <= 2.5
The .next() method of iterators immediately raises StopIteration if the iterator immediately finishes -- i.e., for your use case, if no item in the iterable satisfies the condition. If you don't care (i.e., you know there must be at least one satisfactory item) then just use .next() (best on a genexp, line for the next built-in in Python 2.6 and better).
If you do care, wrapping things in a function as you had first indicated in your Q seems best, and while the function implementation you proposed is just fine, you could alternatively use itertools, a for...: break loop, or a genexp, or a try/except StopIteration as the function's body, as various answers suggested. There's not much added value in any of these alternatives so I'd go for the starkly-simple version you first proposed.
Damn Exceptions!
I love Alex Martelli's answer. However, since next() raise a StopIteration exception when there are no items,
i would use the following snippet to avoid an exception:
a = []
item = next((x for x in a), None)
For example,
a = []
item = next(x for x in a)
Will raise a StopIteration exception;
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration