【问题标题】:Find the index of the n'th item in a list查找列表中第 n 个项目的索引
【发布时间】:2012-01-10 08:17:40
【问题描述】:

我想查找列表中第 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 问题实施 Eratosthenes 筛法时第一次遇到此问题,但这是我在其他情况下遇到的更普遍的问题。

编辑:我得到了很多很好的答案,所以我决定做一些性能测试。下面是 timeit 以秒为单位的列表的执行时间,其中 len nelements 搜索第 4000'th/1000'th True。列表是随机的真/假。源代码链接如下;有点乱。除了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 检查会节省时间。此外,在搜索数字而不是 True/False 时,graddy's 效率更高......不清楚为什么会这样。 eyquem 的解决方案本质上等同于其他的解决方案,但开销或多或少; eyquem_occur 与 taymon 的解大致相同,而 eyquem_occurrence 与 listcomp 类似。

【问题讨论】:

  • 编辑:我之前的评论假设您问的是不同的问题,而不是关于语法的问题。对不起。我不是 Python 人,但似乎应该能够用 for 循环计算你想要的次数,每次都增加你的计数器。将其封装在一个 while 循环中。所以 while(amountOfTrues
  • +1 以获得关于答案比较的优秀文章。干得好!

标签: python arrays performance numpy indexing


【解决方案1】:

我不能肯定这是最快的方式,但我想它会很好:

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

答案是i

【讨论】:

  • 好点...在大多数情况下,这可能比完整列表理解更有效。
  • +1 干得好。这是一个干净的解决方案,最大限度地利用 list.indexstart 参数:-)
【解决方案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

【讨论】:

    【解决方案3】:

    如果您关心性能,最好看看是否可以进行算法优化。例如,如果您在相同的值上多次调用此函数,您可能希望缓存以前的计算(例如,一旦您找到一个元素的第 50 次出现,您可以在O(1) 时间找到任何以前的出现)。

    否则,您需要确保您的技术适用于(惰性)迭代器。

    我能想到的最**优雅和性能满意的实现方式是:

    def indexOfNthOccurrence(N, element, stream):
        """for N>0, returns index or None"""
        seen = 0
        for i,x in enumerate(stream):
            if x==element:
                seen += 1
                if seen==N:
                    return i
    

    (如果你真的关心 enumerate 和其他技术的性能差异,你需要使用 profiling,尤其是 numpy 函数,可能会使用 C)

    预处理整个流并支持O(1)查询:

    from collections import *
    cache = defaultdict(list)
    for i,elem in enumerate(YOUR_LIST):
        cache[elem] += [i]
    
    # e.g. [3,2,3,2,5,5,1]
    #       0 1 2 3 4 5 6
    # cache: {3:[0,2], 1:[6], 2:[1,3], 5:[4,5]}
    

    【讨论】:

      【解决方案4】:

      @Taymon 使用 list.index 给出的答案很棒。

      FWIW,这是一种使用 itertools module 的函数式方法。它适用于任何可迭代的输入,而不仅仅是列表:

      >>> 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 的 eval 循环——一切都以 C 速度完成,内存占用很小,使用惰性求值,没有变量分配,以及可单独测试的组件。 IOW,这是函数式程序员梦寐以求的一切:-)

      示例运行:

      >>> 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
      

      【讨论】:

      • 我喜欢它,虽然我倾向于将第一个子计算拆分为“def item_indices(iterable, item):”所以我可以给它一个文档字符串。
      • 太棒了。现在为什么不是内置的list 方法?
      • 旁注:是否可以在 python 2.6 中安装 itertools 2.7?还是存在根本的不兼容?也许我应该把这个作为一个不同的问题来问......
      • @keflavich 我不知道在不重建 Python 的情况下反向移植 2.7 itertools 的简单方法,但您可以实现纯 Python 等效项,如 2.7 itertools 文档中所示。试试这个:compress = lambda data, selectors: (d for d, s in izip(data, selectors) if s).
      【解决方案5】:
      [y for y in enumerate(x) if y[1]==True][z][0]
      

      注意:这里 Z 是第 n 次出现,

      【讨论】:

      • 非常优雅。一个更清晰的版本符合我的口味:[i for i, e in enumerate(x) if e == True][z].
      【解决方案6】:

      首先创建一个列表对象并返回该列表的第n-1个元素的解决方案:函数occurence()

      我认为,还有一个解决方案也可以实现函数式程序员的梦想,使用生成器,因为我喜欢它们:function occur()

      S = 'stackoverflow.com is a fantastic amazing site'
      print 'object S is string %r' % S
      print "indexes of 'a' in S :",[indx for indx,elem in enumerate(S) if elem=='a']
      
      def occurence(itrbl,x,nth):
          return [indx for indx,elem in enumerate(itrbl)
                  if elem==x ][nth-1] if x in itrbl \
                 else None
      
      def occur(itrbl,x,nth):
          return (i for pos,i in enumerate(indx for indx,elem in enumerate(itrbl)
                                           if elem==x)
                  if pos==nth-1).next() if x in itrbl\
                  else   None
      
      print "\noccurence(S,'a',4th) ==",occurence(S,'a',4)
      print "\noccur(S,'a',4th) ==",occur(S,'a',4)
      

      结果

      object S is string 'stackoverflow.com is a fantastic amazing site'
      indexes of 'a' in S : [2, 21, 24, 27, 33, 35]
      
      occur(S,'a',4th) == 27
      
      occurence(S,'a',4th) == 27
      

      第二种解决方案看起来很复杂,但实际上并非如此。它不需要完全遍历可迭代对象:一旦找到所需的事件,进程就会停止。

      【讨论】:

        【解决方案7】:

        这是在列表itrbl 中查找nth 出现的x 的另一种方法:

        def nthoccur(nth,x,itrbl):
            count,index = 0,0
            while count < nth:
                if index > len(itrbl) - 1:
                    return None
                elif itrbl[index] == x:
                    count += 1
                    index += 1
                else:
                    index += 1
            return index - 1
        

        【讨论】:

          【解决方案8】:

          这是一种方法:
          对于上面的例子:

          x=[False,True,True,False,True,False,True,False,False,False,True,False,True]
          

          我们可以定义一个函数 find_index

          def find_index(lst, value, n):
              c=[]
              i=0
              for element in lst :
                    if element == value :
                        c .append (i)
                    i+=1    
              return c[n]
          

          如果我们应用这个函数:

          nth_index = find_index(x, True, 4)
          print nth_index
          

          结果是:

          10
          

          【讨论】:

            【解决方案9】:

            我认为这应该可行。

            def get_nth_occurrence_of_specific_term(my_list, term, n):
                assert type(n) is int and n > 0
                start = -1
                for i in range(n):
                    if term not in my_list[start + 1:]:
                        return -1
                    start = my_list.index(term, start + 1)
                return start
            

            【讨论】:

              【解决方案10】:

              您可以将nextenumerate 和生成器表达式一起使用。 itertools.islice 允许您根据需要对可迭代对象进行切片。

              from itertools import islice
              
              x = [False,True,True,False,True,False,True,False,False,False,True,False,True]
              
              def get_nth_index(L, val, n):
                  """return index of nth instance where value in list equals val"""
                  return next(islice((i for i, j in enumerate(L) if j == val), n-1, n), -1)
              
              res = get_nth_index(x, True, 3)  # 4
              

              如果迭代器耗尽,即指定值的第n次出现不存在,next可以返回一个默认值,在这种情况下-1

              【讨论】:

                【解决方案11】:

                你可以使用count:

                from itertools import count
                
                x = [False, True, True, False, True, False, True, False, False, False, True, False, True]
                
                
                def nth_index(n, item, iterable):
                    counter = count(1)
                    return next((i for i, e in enumerate(iterable) if e == item and next(counter) == n), -1)
                
                
                print(nth_index(3, True, x))
                

                输出

                4
                

                这个想法是,由于e == item and next(counter) == n) 的短路特性,表达式next(counter) == n 仅在e == item 时被评估,因此您只计算等于item 的元素。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-04-11
                  • 2010-09-15
                  • 1970-01-01
                  • 2021-04-14
                  • 2016-01-25
                  • 2014-10-13
                  • 2018-07-31
                  相关资源
                  最近更新 更多