在列表中找到第n个项目的索引
我想在一个列表中找到某个项目出现的第 n 次的位置。例如,
x=[False,True,True,False,True,False,True,False,False,False,True,False,True]
第 n 次出现的“真”值的索引是什么?如果我想要第五次出现(如果从零开始算就是第四次),答案是 10。
我想到了:
indargs = [ i for i,a in enumerate(x) if a ]
indargs[n]
注意,x.index
只会返回第一次出现的位置,或者在某个点之后的第一次出现,因此我觉得这不是一个解决方案。
对于类似的情况,numpy 也有解决方案,比如使用 cumsum
和 where
,但我想知道有没有不使用 numpy 的方法来解决这个问题。
我对性能有些担心,因为我第一次遇到这个问题时是在实现埃拉托斯特尼筛法来解决一个 Project Euler 的问题,但这也是我在其他情况下遇到的一个更普遍的问题。
编辑:我收到了很多很好的答案,所以我决定做一些性能测试。下面是 timeit
执行时间(以秒为单位),用于查找长度为 len
的列表中第 4000 次/第 1000 次出现的“真”值。这些列表是随机的“真/假”。源代码链接在下面;有点乱。我使用了简短/修改过的名字来描述函数,除了 listcomp
,它是上面提到的简单列表推导。
True Test (100'th True in a list containing True/False)
nelements eyquem_occur eyquem_occurrence graddy taymon listcomp hettinger26 hettinger
3000: 0.007824 0.031117 0.002144 0.007694 0.026908 0.003563 0.003563
10000: 0.018424 0.103049 0.002233 0.018063 0.088245 0.003610 0.003769
50000: 0.078383 0.515265 0.002140 0.078074 0.442630 0.003719 0.003608
100000: 0.152804 1.054196 0.002129 0.152691 0.903827 0.003741 0.003769
200000: 0.303084 2.123534 0.002212 0.301918 1.837870 0.003522 0.003601
True Test (1000'th True in a list containing True/False)
nelements eyquem_occur eyquem_occurrence graddy taymon listcomp hettinger26 hettinger
3000: 0.038461 0.031358 0.024167 0.039277 0.026640 0.035283 0.034482
10000: 0.049063 0.103241 0.024120 0.049383 0.088688 0.035515 0.034700
50000: 0.108860 0.516037 0.023956 0.109546 0.442078 0.035269 0.035373
100000: 0.183568 1.049817 0.024228 0.184406 0.906709 0.035135 0.036027
200000: 0.333501 2.141629 0.024239 0.333908 1.826397 0.034879 0.036551
True Test (20000'th True in a list containing True/False)
nelements eyquem_occur eyquem_occurrence graddy taymon listcomp hettinger26 hettinger
3000: 0.004520 0.004439 0.036853 0.004458 0.026900 0.053460 0.053734
10000: 0.014925 0.014715 0.126084 0.014864 0.088470 0.177792 0.177716
50000: 0.766154 0.515107 0.499068 0.781289 0.443654 0.707134 0.711072
100000: 0.837363 1.051426 0.501842 0.862350 0.903189 0.707552 0.706808
200000: 0.991740 2.124445 0.498408 1.008187 1.839797 0.715844 0.709063
Number Test (750'th 0 in a list containing 0-9)
nelements eyquem_occur eyquem_occurrence graddy taymon listcomp hettinger26 hettinger
3000: 0.026996 0.026887 0.015494 0.030343 0.022417 0.026557 0.026236
10000: 0.037887 0.089267 0.015839 0.040519 0.074941 0.026525 0.027057
50000: 0.097777 0.445236 0.015396 0.101242 0.371496 0.025945 0.026156
100000: 0.173794 0.905993 0.015409 0.176317 0.762155 0.026215 0.026871
200000: 0.324930 1.847375 0.015506 0.327957 1.536012 0.027390 0.026657
Hettinger 的 itertools 解决方案几乎总是最佳选择。taymon 和 graddy 的解决方案在大多数情况下是次优的,虽然在你想要第 n 次出现且 n 较高,或者列表中出现次数少于 n 时,列表推导的方法可能更好。如果有可能出现次数少于 n,最开始的 count
检查可以节省时间。此外,graddy 的方法在查找数字而不是“真/假”时效率更高……不太清楚为什么会这样。eyquem 的解决方案基本上与其他方案相当,只是开销略多或略少;eyquem_occur 大致与 taymon 的解决方案相同,而 eyquem_occurrence 则类似于 listcomp。
11 个回答
如果效率很重要,我觉得用普通的循环(O(N))会比列表推导式更好,因为列表推导式的时间复杂度是O(L),其中L是列表的长度。
举个例子:假设你有一个非常大的列表,你想找到第一个出现的数字N=1,显然,一旦找到第一个出现的地方就应该立刻停止,这样更有效率。
count = 0
for index,i in enumerate(L):
if i:
count = count + 1
if count==N:
return index
我不能确定这是不是最快的方法,但我觉得应该还不错:
i = -1
for j in xrange(n):
i = x.index(True, i + 1)
答案是 i
。
@Taymon 提供的使用 list.index 的答案非常好。
顺便说一下,这里有一个使用 itertools 模块 的函数式方法。它可以处理任何可迭代的输入,不仅仅是列表:
>>> from itertools import compress, count, imap, islice
>>> from functools import partial
>>> from operator import eq
>>> def nth_item(n, item, iterable):
indicies = compress(count(), imap(partial(eq, item), iterable))
return next(islice(indicies, n, None), -1)
这个例子很不错,因为它展示了如何有效地结合 Python 的函数式工具。需要注意的是,一旦设置好这个流程,就不需要再回到 Python 的评估循环中——所有操作都是以 C 语言的速度完成的,内存占用也很小,采用懒惰求值的方式,没有变量赋值,并且各个部分可以单独测试。换句话说,这就是函数式程序员梦寐以求的 :-)
示例运行:
>>> x = [False,True,True,False,True,False,True,False,False,False,True,False,True]
>>> nth_item(50, True, x)
-1
>>> nth_item(0, True, x)
1
>>> nth_item(1, True, x)
2
>>> nth_item(2, True, x)
4
>>> nth_item(3, True, x)
6