在列表中找到第n个项目的索引

40 投票
11 回答
15469 浏览
提问于 2025-04-17 07:26

我想在一个列表中找到某个项目出现的第 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 也有解决方案,比如使用 cumsumwhere,但我想知道有没有不使用 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 个回答

2

如果效率很重要,我觉得用普通的循环(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
27

我不能确定这是不是最快的方法,但我觉得应该还不错:

i = -1
for j in xrange(n):
    i = x.index(True, i + 1)

答案是 i

35

@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

撰写回答