检查列表中的n个项目是否与python中的条件匹配的最快方法

时间:2013-12-21 00:56:16

标签: python

如果我在列表中有 m 个项目,那么检查列表中这些项目的 n 是否符合某个条件的最快方法是什么?例如:

l = [1,2,3,4,5]

如何检查列表中的任何两项是否与条件x%2 == 0匹配?

天真的方法是使用嵌套for循环:

for i in l:
    for j in l:
        if not i%2 and not j%2:
            return True

但这是一种非常低效的检查方式,如果我想检查200万至100万件物品清单中的任何50,000件物品,会变得特别难看。

11 个答案:

答案 0 :(得分:8)

[编辑反映完全匹配,我们仍然可以通过短路完成!]

我认为你想让它短路(确定时停止,不仅在最后):

matched = 0
for i in l:
    if i%2 == 0:
        matched += 1
        if matched > 2: # we now have too many matches, stop checking
            break
if matched == 2:
    print("congratulations")

如果你想在相同的输入数据上多次快速地进行查询,你应该改为使用NumPy(没有短路):

l = np.array([1,2,3,4,5])

if np.count_nonzero(l%2 == 0) == 2:
    print "congratulations"

这不会短路,但是一旦构造了输入数组,它就会超快,所以如果你有一个很大的输入列表和很多查询,那么查询就不会短 - 电路很早,这可能会更快。可能是一个数量级。

答案 1 :(得分:4)

添加sum值的True解决方案是正确的,可能比显式循环更有效,而且最简洁:

if sum(i % 2 == 0 for i in lst) == n:

然而,它依赖于理解在添加的整数上下文中,True计为1False计为0。你可能不想指望它。在这种情况下你可以重写它(squiguy的答案):

if sum(1 for i in lst if i % 2 == 0) == n:

但是你可能想把它分解成一个函数:

def count_matches(predicate, iterable):
    return sum(predicate(i) for i in iterable)

此时,filter列表可能更具可读性,并计算得到的过滤迭代的长度:

def ilen(iterable):
    return sum(1 for _ in iterable)

def count_matches(predicate, iterable):
    return ilen(filter(predicate, iterable))

然而,所有这些变体的缺点 - 与使用mapfilter一样,你的谓词必须是函数,而不仅仅是表达。当你只想检查some_function(x)是否返回True时,这很好,但是当你想检查x % 2 == 0时,你必须转到将其包装在函数中的额外步骤,如下所示:

if count_matches(lambda x: x %2 == 0, lst) == n

......在这一点上,我认为你失去了比你获得的更多的可读性。


因为你要求最快 - 尽管这可能是错误的,因为我确信这些解决方案中的任何一个对于几乎任何应用都足够快,而且这不太可能是一个热点 - 无论如何这里是一些测试我的计算机上的64位CPython 3.3.2,长度为250:

32.9 µs: sum(not x % 2 for x in lst)
33.1 µs: i=0\nfor x in lst: if not x % 2: i += 1\n
34.1 µs: sum(1 for x in lst if not x % 2)
34.7 µs: i=0\nfor x in lst: if x % 2 == 0: i += 1\n
35.3 µs: sum(x % 2 == 0 for x in lst)
37.3 µs: sum(1 for x in lst if x % 2 == 0)
52.5 µs: ilen(filter(lambda x: not x % 2, lst))
56.7 µs: ilen(filter(lambda x: x % 2 == 0, lst))

所以,事实证明,至少在64位CPython 3.3.2中,你是否使用显式循环,总结False和True,或者如果True产生很小的差异则总结1s;使用not代替== 0在某些情况下会比其他情况产生更大的差异;但即便是最糟糕的只比最好的情况差12%。

所以我会使用你认为最具可读性的那个。并且,如果最慢的一个不够快,最快的可能也不是,这意味着您可能需要重新安排您的应用程序以使用NumPy,在PyPy而不是CPython中运行您的应用程序,编写自定义Cython或C代码,或者做一些比重新组织这个简单算法更激烈的事情。

为了比较,这里有一些NumPy实现(假设lstnp.ndarray而不是list):

 6.4 µs: len(lst) - np.count_nonzero(lst % 2)
 8.5 µs: np.count_nonzero(lst % 2 == 0)
17.5 µs: np.sum(lst % 2 == 0)

即使对NumPy最明显的翻译速度几乎是其两倍;通过一些工作,你可以使它快3倍。

这是在PyPy(3.2.3 / 2.1b1)中运行完全相同的代码而不是CPython的结果:

14.6 µs: sum(not x % 2 for x in lst)

速度提高两倍以上,代码完全没有变化。

答案 2 :(得分:3)

您可能需要查看numpy

例如:

In [16]: import numpy as np 
In [17]: a = np.arange(5)

In [18]: a
Out[18]: array([0, 1, 2, 3, 4])

In [19]: np.sum(a % 2 == 0)
Out[19]: 3

时序:

In [14]: %timeit np.sum(np.arange(100000) % 2 == 0)
100 loops, best of 3: 3.03 ms per loop

In [15]: %timeit sum(ele % 2 == 0 for ele in range(100000))
10 loops, best of 3: 17.8 ms per loop

但是,如果您考虑从list转换为numpy.arraynumpy并不快:

In [20]: %timeit np.sum(np.array(range(100000)) % 2 == 0)
10 loops, best of 3: 23.5 ms per loop

修改

@ abarnert的解决方案是最快的:

In [36]: %timeit(len(np.arange(100000)) - np.count_nonzero(a % 2))
10000 loops, best of 3: 80.4 us per loop

答案 3 :(得分:1)

我会使用while循环:

l=[1,2,3,4,5]

mods, tgt=0,2
while mods<tgt and l:
    if l.pop(0)%2==0:
        mods+=1

print(l,mods)  

如果您担心“最快”&#39;用deque替换列表:

from collections import deque

l=[1,2,3,4,5]
d=deque(l)
mods, tgt=0,2
while mods<tgt and d:
    if d.popleft()%2==0: mods+=1

print(d,mods)     

在任何一种情况下,都很容易阅读,并在满足条件时短路。

确实与短路写完全匹配:

from collections import deque

l=[1,2,3,4,5,6,7,8,9]
d=deque(l)
mods, tgt=0,2
while mods<tgt and d:
    if d.popleft()%2==0: mods+=1

print(d,mods,mods==tgt)
# deque([5, 6, 7, 8, 9]) 2 True
# answer found after 4 loops


from collections import deque

l=[1,2,3,4,5,6,7,8,9]
d=deque(l)
mods, tgt=0,2
while mods<tgt and d:
    if d.popleft()%9==0: mods+=1

print(d,mods,mods==tgt)
# deque([]) 1 False
# deque exhausted and less than 2 matches found...

您还可以在列表上使用迭代器:

l=[1,2,3,4,5,6,7,8,9]
it=iter(l)
mods, tgt=0,2
while mods<tgt:
    try:
        if next(it)%2==0: mods+=1
    except StopIteration:
        break

print(mods==tgt)   
# True

答案 4 :(得分:1)

您可以根据条件使用内置的sum,并检查它是否等于n值。

l = [1, 2, 3, 4, 5]
n = 2
if n == sum(1 for i in l if i % 2 == 0):
    print(True)

答案 5 :(得分:1)

为什么不使用filter()?

例:检查列表中偶数的整数:

>>> a_list = [1, 2, 3, 4, 5]
>>> matches = list(filter(lambda x: x%2 == 0, a_list))
>>> matches
[2, 4]

然后如果你想要匹配的数量:

>>> len(matches)
2

最后你的回答:

>>> if len(matches) == 2:
        do_something()

答案 6 :(得分:1)

为每个符合条件的项目构建一个返回1的生成器,并将该生成器限制为最多n + 1个项目,并检查这些项目的总和是否等于您的数字之后,例如:

from itertools import islice

data = [1,2,3,4,5]
N = 2
items = islice((1 for el in data if el % 2 == 0), N + 1)
has_N = sum(items) == N

答案 7 :(得分:0)

这有效:

>>> l = [1,2,3,4,5]
>>> n = 2
>>> a = 0  # Number of items that meet the condition
>>> for x in l:
...     if x % 2 == 0:
...         a += 1
...         if a > n:
...             break
...
>>> a == n
True
>>>

它的优点是只运行一次列表。

答案 8 :(得分:0)

Itertools是列表拖钓任务的有用快捷方式

import itertools

#where expr is a lambda, such as 'lambda a: a % 2 ==0'
def exact_match_count ( expr, limit,  *values):
    passes = itertools.ifilter(expr, values)
    counter = 0
    while counter <= limit + 1:
        try:
            passes.next()
            counter +=1
        except:
            break
    return counter == limit

如果您关注内存限制,请调整签名,以便*值是生成器而不是元组

答案 9 :(得分:0)

任何“最快解决方案”的候选人都需要对输入进行一次传递并提前退出。

这是解决方案的良好基线起点:

>>> s = [1, 2, 3, 4, 5]
>>> matched = 0
>>> for x in s:
        if x % 2 == 0:
            matched += 1
            if matched > 2:
                print 'More than two matched'
else:
    if matched == 2:
        print 'Exactly two matched'
    else:
        print 'Fewer than two matched'


Exactly two matched

以下是改进算法正确的基线解决方案的一些想法:

  1. 优化条件的计算。例如,将x % 2 == 0替换为not x & 1。这称为reduction in strength

  2. 本地化变量。由于全局查找和赋值比局部变量赋值更昂贵,因此如果它位于函数内,则完全匹配测试将运行得更快。

    例如:

    def two_evens(iterable):
        'Return true if exactly two values are even'
        matched = 0
        for x in s:
                if x % 2 == 0:
                    matched += 1
                    if matched > 2:
                        return False
        return matched == 2
    
  3. 使用itertools驱动循环逻辑来删除解释器开销。

    例如,itertools.ifilter()可以以C-speed隔离匹配:

    >>> list(ifilter(None, [False, True, True, False, True]))
    [True, True, True]
    

    同样,itertools.islice()可以C速度实现早期逻辑:

    >>> list(islice(range(10), 0, 3))
    [0, 1, 2]
    

    内置sum()功能可以以C速度计算匹配。

    >>> sum([True, True, True])
    3
    

    将这些放在一起以检查确切的匹配数量:

    >>> s = [False, True, False, True, False, False, False]
    >>> sum(islice(ifilter(None, s), 0, 3)) == 2
    True
    
  4. 如果这是实际程序中的实际瓶颈,那么这些优化是值得的。这通常只会在您要进行许多此类精确匹配计数测试时才会发生。如果是这样,那么可以通过在第一遍中缓存一些中间结果然后在后续测试中重用它们来节省额外的费用。

    例如,如果存在复杂条件,则可能会缓存并重复使用子条件结果。

    而不是:

    check_exact(lambda x: x%2==0 and x<10 and f(x)==3, dataset, matches=2)
    check_exact(lambda x: x<10 and f(x)==3, dataset, matches=4)
    check_exact(lambda x: x%2==0 and f(x)==3, dataset, matches=6)
    

    预先计算所有条件(每个数据值只有一次):

    evens = map(lambda x: x%2==0, dataset)
    under_tens = map(lambda x: x<10, dataset)
    f_threes = map(lambda x: x%2==0 and f(x)==3, dataset)
    

答案 10 :(得分:0)

一种简单的方法:

def length_is(iter, size):
    for _ in xrange(size - 1):
        next(iter, None)

    try:
        next(iter)
    except StopIteration:
        return False  # too few

    try:
        next(iter)
        return False  # too many
    except StopIteration:
        return True
length_is((i for i in data if x % 2 == 0), 2)

这是一种稍微愚蠢的写作方式:

class count(object):
    def __init__(self, iter):
        self.iter = iter

    __eq__ = lambda self, n: length_is(self.iter, n)

,并提供:

count(i for i in data if x % 2 == 0) == 2