在列表中找到第一个满足条件的元素及其索引

3
考虑这个简单的例子。
mylist = [-1,-2,3,4,5,6]

for idx, el in enumerate(mylist):
    if el > 0:
        myidx, myel = idx, el
        break

myidx, myel
Out[20]: (2, 3)

我想找到一个python列表中第一次出现满足特定条件(这里是简单的>0)的索引和对应的元素。

在上面的代码中,我使用enumerate循环遍历元素,然后使用if子句查找正确的元素。这对我来说看起来非常麻烦。有更好的方法吗?例如使用本机python函数?

谢谢!


问题中描述的方法(使用命令式for循环)具有明显的优势,即在满足条件时立即中断迭代,并且不需要在内存中使用中间数据结构。下面答案中的列表推导版本几乎总是会遍历整个列表,然后再检查条件,并且需要一个或多个中间数据结构(长度相等的新列表)在内存中。对于小型列表来说,这些都无关紧要,但对于大型列表来说可能非常重要。 - undefined
3个回答

3

这样的代码应该可以运行:

l = [-1,-2,3,4,5,6]
list(x > 0 for x in l).index(True)
# Output: 2

为了查找所有的模式,我们可以使用Python内置函数。使用如下代码:
from itertools import filterfalse
f = filterfalse(lambda x: x[1] <= 0, enumerate(l))
print(list(f))
# [(2, 1), (3, 2), (4, 3)]

有趣,但我不明白这里发生了什么。难道列表返回的不是True或False的布尔值吗? - undefined
它逐个检查元素,并检查子句 x > 0。因此,列表转换为 [False, False, True, .....]。然后,index() 在列表中找到第一个 True 元素。 - undefined
啊,是的,但我对索引和元素都感兴趣! - undefined
@ℕʘʘḆḽḘ 使用Python内置的filterfalse函数可以实现。 - undefined

2
你可以使用列表推导式来实现。这基本上与你的代码相同,但压缩成一行,并构建一个符合条件的结果列表。
第一种方法获取所有匹配项。
mylist = [-1,-2,3,4,5,6]

results = [(i, el) for i, el in enumerate(mylist) if el > 0]

另一种方法是使用生成器表达式,这可能更快,并且只需解包即可。这将获取第一个。
*next((i, el) for i, el in enumerate(mylist) if el > 0))

这段代码循环遍历列表并检查条件,然后将索引和元素放入元组中。将其放在括号内会将其转换为生成器,这样做速度更快,因为它实际上没有必要将所有内容保存在内存中,它只是在需要时生成响应。使用next()可以迭代它们。由于我们仅在此处使用了一次next(),因此它只生成第一个匹配项。然后我们使用*进行解包。
由于这里还有另外两个有效答案,我决定使用timeit模块来计算每个答案的时间并发布结果。为了清晰起见,我还计时了OP的方法。以下是我的发现:
import timeit
# Method 1 Generator Expression
print(timeit.timeit('next((i, el) for i, el in enumerate([-1,-2,3,4,5,6]) if el > 0)', number=100000))
0.007089499999999999

# Method 2 Getting index of True
print(timeit.timeit('list(x > 0 for x in [-1,-2,3,4,5,6]).index(True)', number=100000))
0.008104599999999997

# Method 3 filter and lambda
print(timeit.timeit('myidx , myel = list(filter(lambda el: el[1] > 0, enumerate([-1,-2,3,4,5,6])))[0]', number=100000))
0.0155314

statement = """
for idx, el in enumerate([-1,-2,3,4,5,6]):
    if el > 0:
        myidx, myel = idx, el
        break
"""

print(timeit.timeit(statement, number=100000))
0.04074070000000002


不错,但是多个匹配怎么办? - undefined
这段代码应该能找到所有大于0的元素,并返回一个包含索引和元素的元组列表。我还没有实际运行过它。 - undefined
啊等一下,我刚刚运行了它并且正在比较索引。我现在会进行编辑,以便输出与你的相同。 - undefined
是的,我没有意识到你只想要一个结果。在这种情况下,生成器表达式是最好的方法。 - undefined

1
你可以这样利用lambdafilter的组合:
mylist = [-1,-2,3,4,5,6]

myidx, myel = list(filter(lambda el: el[1] > 0, enumerate(mylist)))[0]
print("({}, {})".format(myidx, myel))

解释:

filter()函数提供了一种优雅的过滤元素的方法,它接受一个函数和一个列表作为参数。这里的函数是lambda,列表是mylist。由于我们想要获取相应的索引,所以需要使用enumerate来包装enumerate(mylist)

基本上,enumerate(mylist)返回一个索引和相应值的元组。我们的条件是值与0之间的比较,所以我们得到的是el[1]而不是el[0]0进行比较。

结果将转换为list。此列表包括所有符合条件的(index, value)对。这里我们想要获取第一个对,所以在末尾加上了[0]

输出:

(2, 3)

有趣,谢谢!你能再解释一下这里的逻辑吗? - undefined
我更新了我的回答,并添加了解释。 - undefined
这种方法的缺点是即使结果可能已经在之前的元素中找到,也需要迭代整个列表。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接