有效地检查元素是否在列表中至少出现n次

时间:2016-10-31 21:59:19

标签: python performance list python-3.x optimization

如何最好地编写Python函数(check_list)以有效地测试元素(x)是否在列表中n次出现l)?

我的第一个想法是:

def check_list(l, x, n):
    return l.count(x) >= n

但是,x找到n次并且始终为O(n)时,这不会发生短路。

一种简单的短路方法是:

def check_list(l, x, n):
    count = 0
    for item in l:
        if item == x:
            count += 1
            if count == n:
                return True
    return False

我还有一个更紧凑的带发电机的短路解决方案:

def check_list(l, x, n):
    gen = (1 for item in l if item == x)
    return all(next(gen,0) for i in range(n))

还有其他好的解决方案吗?什么是最有效的方法?

谢谢

7 个答案:

答案 0 :(得分:22)

您可以使用{{3},而不是通过设置range对象并使用必须测试每个项目的真实性all来产生额外开销}提前生成n步骤,然后如果切片存在则返回切片中的 next 项,否则返回默认False

from itertools import islice

def check_list(lst, x, n):
    gen = (True for i in lst if i==x)
    return next(islice(gen, n-1, None), False)

请注意,与list.count类似,itertools.islice也以C速度运行。这具有处理非列表的迭代的额外优势。

有些时间:

In [1]: from itertools import islice

In [2]: from random import randrange

In [3]: lst = [randrange(1,10) for i in range(100000)]

In [5]: %%timeit # using list.index
   ....: check_list(lst, 5, 1000)
   ....:
1000 loops, best of 3: 736 µs per loop

In [7]: %%timeit # islice
   ....: check_list(lst, 5, 1000)
   ....:
1000 loops, best of 3: 662 µs per loop

In [9]: %%timeit # using list.index
   ....: check_list(lst, 5, 10000)
   ....:
100 loops, best of 3: 7.6 ms per loop

In [11]: %%timeit # islice
   ....: check_list(lst, 5, 10000)
   ....:
100 loops, best of 3: 6.7 ms per loop

答案 1 :(得分:13)

您可以使用index的第二个参数来查找后续的出现指数:

def check_list(l, x, n):
    i = 0
    try:
        for _ in range(n):
            i = l.index(x, i)+1
        return True
    except ValueError:
        return False

print( check_list([1,3,2,3,4,0,8,3,7,3,1,1,0], 3, 4) )

关于index参数

官方文档在其Python Tutuorial, section 5方法的第二或第三个参数中未提及,但您可以在更全面的Python Standard Library, section 4.6中找到它:

  在 s 中首次出现 x

s.index(x[, i[, j]])索引(在索引 i 之后或之后 Ĵ(8)

     

(8) index s 中找不到 x 时会引发ValueError。支持时,索引方法的附加参数允许有效搜索序列的子部分。传递额外的参数大致相当于使用s[i:j].index(x),只是没有复制任何数据,并且返回的索引是相对于序列的开头而不是切片的开头。

性能比较

在将此list.index方法与islice(gen)方法进行比较时,最重要的因素是要找到的事件之间的距离。一旦该距离平均为13或更高,list.index具有更好的性能。对于较低距离,最快的方法还取决于要查找的出现次数。找到的次数越多,islice(gen)方法在平均距离方面越早出现list.index:当出现次数变得非常大时,此增益会逐渐消失。

下图绘制(近似)边界线,两种方法的表现同样良好(X轴为对数):

enter image description here

答案 2 :(得分:2)

如果您预计大量案例将导致提前终止,最终短路是可行的方法。让我们探讨一下可能性:

list.index方法与list.count方法为例(根据我的测试,这两个方法最快,但是ymmv)

对于list.index,如果列表包含n个或更多个x,则该方法被调用n次。在list.index方法中,执行速度非常快,允许比自定义生成器快得多的迭代。如果x的出现相距足够远,那么index的低级执行将会看到大的加速。如果x的实例靠得很近(更短的列表/更常见的x),那么将花费更多的时间来执行调解函数其余部分的较慢的python代码(循环n并递增{{1} })

i的好处是它可以在慢速python执行之外完成所有繁重工作。这是一个更容易分析的功能,因为它只是O(n)时间复杂度的一个例子。通过在python解释器中几乎没有花费任何时间,但是对于短列表而言,它几乎是最快的。

选择标准摘要:

  • 较短的列表赞成list.count
  • 任何不太可能短路偏好的长度列表list.count
  • 列出很长且可能短路的list.count

答案 3 :(得分:0)

我建议您使用Counter模块中的collections

from collections import Counter

%%time
[k for k,v in Counter(np.random.randint(0,10000,10000000)).items() if v>1100]

#Output:
    Wall time: 2.83 s
    [1848, 1996, 2461, 4481, 4522, 5844, 7362, 7892, 9671, 9705]

答案 4 :(得分:0)

这显示了另一种方法。

  1. 对列表进行排序。
  2. 找到项目第一次出现的索引。
  3. 将索引增加一个小于项目必须出现的次数。 (n - 1)
  4. 查找该索引处的元素是否与您要查找的项目相同。

    def check_list(l, x, n):
        _l = sorted(l)
        try:
            index_1 = _l.index(x)
            return _l[index_1 + n - 1] == x
        except IndexError:
            return False
    

答案 5 :(得分:-1)

                                       c=0
                                       for i in l:
                                           if i==k:
                                              c+=1
                                       if c>=n:
                                          print("true")
                                       else:
                                          print("false")

答案 6 :(得分:-2)

另一种可能性可能是:

def check_list(l, x, n):
    return sum([1 for i in l if i == x]) >= n