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?.
How can I find a value in a list using Python? - Ask a Question - TestMu AI (formerly LambdaTest) Community
pandas - How to define a python lambda getting the first element? - Stack Overflow
python - Get the first item from an iterable that matches a condition - Stack Overflow
Filter a Python list by predicate - Stack Overflow
Videos
If need select first value in group is necessary use Series.iat or Series.iloc for select by position:
df1 = df.groupby('A', as_index = False).agg({'B':'mean', 'C': lambda x: x.iat[0]})
Another solution is use GroupBy.first:
df1 = df.groupby('A', as_index = False).agg({'B':'mean', 'C': 'first'})
print (df1)
A B C
0 0 2 alpha
1 1 9 charlie
Can you add an explanation of why the lambda doesn't work?
Problem is for second group, there is index not 0, but 2, what raise error, because x[0] try seelct by index with 0 and for second group it not exist:
df1 = df.groupby('A', as_index = False).agg({'B':'mean', 'C': lambda x: print (x[0])})
print (df1)
alpha <- return first value of first group only, because alpha has index 0
alpha
alpha
So if set index 0 for first values of groups it working with this sample data:
df = pd.DataFrame({'A': [0, 0, 1, 1],
'B': [1, 3, 8, 10],
'C': ['alpha', 'bravo', 'charlie', 'delta']}, index=[0,1,0,1])
print (df)
A B C
0 0 1 alpha <- index is 0
1 0 3 bravo
0 1 8 charlie <- index is 0
1 1 10 delta
df1 = df.groupby('A', as_index = False).agg({'B':'mean', 'C': lambda x: x[0]})
print (df1)
A B C
0 0 2 alpha
1 1 9 charlie
Small explanation on why your lambda function won't work.
When we use groupby we get an groupby object back:
g = df.groupby('A')
print(g)
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000023AA1BB41D0>
When we access the elements in our groupby object, we get grouped dataframes back:
for idx, d in g:
print(d, '\n')
A B C
0 0 1 alpha
1 0 3 bravo
A B C
2 1 8 charlie
3 1 10 delta
So thats why we need to threat these elements as DataFrames. As jezrael already pointed out in his answer, there are several ways to access the first value in your C column:
for idx, d in g:
print(d['C'].iat[0])
print(d['C'].iloc[0], '\n')
alpha
alpha
charlie
charlie
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
You can use the filter method:
>>> lst = [1, 2, 3, 4, 5]
>>> filter(lambda x: x % 2 == 0, lst)
[2, 4]
or a list comprehension:
>>> lst = [1, 2, 3, 4, 5]
>>> [x for x in lst if x %2 == 0]
[2, 4]
to find a single element, you could try:
>>> next(x for x in lst if x % 2 == 0)
2
Though that would throw an exception if nothing matches, so you'd probably want to wrap it in a try/catch. The () brackets make this a generator expression rather than a list comprehension.
Personally though I'd just use the regular filter/comprehension and take the first element (if there is one).
These raise an exception if nothing is found
filter(lambda x: x % 2 == 0, lst)[0]
[x for x in lst if x %2 == 0][0]
These return empty lists
filter(lambda x: x % 2 == 0, lst)[:1]
[x for x in lst if x %2 == 0][:1]
Generators and list comprehensions are more pythonic than chainable functions.
>>> lst = [i for i in range(1, 6)]
>>> lst
[1, 2, 3, 4, 5]
>>> gen = (x for x in lst if x % 10 == 0)
>>> next(gen, 'not_found')
'not_found'
>>> [x for x in gen]
[]
For example, I use it like this sometimes:
>>> n = next((x for x in lst if x % 10 == 0), None)
>>> if n is None:
... print('Not found')
...
Not found
Otherwise, you can define your utility function oneliners like this:
>>> find = lambda fun, lst: next((x for x in lst if fun(x)), None)
>>> find(lambda x: x % 10 == 0, lst)
>>> find(lambda x: x % 5 == 0, lst)
5
>>> findall = lambda fun, lst: [x for x in lst if fun(x)]
>>> findall(lambda x: x % 5 == 0, lst)
[5]
You can combine ifilter and islice to get just the first matching element.
>>> list(itertools.islice(itertools.ifilter(lambda n: n % 2 == 0, lst), 1))
[8]
However, I wouldn't consider this anyhow more readable or nicer than the original code you posted. Wrapped in a function it will be much nicer though. And since next only returns one element there is no need for islice anymore:
def find(pred, iterable):
return next(itertools.ifilter(pred, iterable), None)
It returns None if no element was found.
However, you still have the rather slow call of the predicate function every loop. Please consider using a list comprehension or generator expression instead:
>>> next((x for x in lst if x % 2 == 0), None)
8
itertools.ifilter() can do this, if you just grab the first element of the resulting iterable.
itertools.ifilter(pred, col1).next()
Similarly, so could a generator object (again, taking the first item out of the resulting generator):
(i for i in col1 if i % 2 == 0).next()
Since both of these are lazy-evaluated, you'll only evaluate as much of the input iterable as is necessary to get to the first element that satisfies the predicate. Note that if nothing matches the predicate, you'll get a StopIteration exception. You can avoid this by using the next() builtin instead:
next((i for i in col1 if i % 2 == 0), None)
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))