簡體   English   中英

檢查列表中是否正好有n個項目與python中的條件匹配的最快方法

[英]Fastest way to check if exactly n items in a list match a condition in 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

但這是一種極其低效的檢查方法,如果我要檢查2到1千萬個項目中的任何50,000個項目,這將變得尤為難看。

[編輯以反映精確匹配,我們仍然可以通過短路來完成!]

我想您希望它短路(確定后停止,不僅結束時停止):

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"

這不會短路,但是一旦構建了輸入數組,它將是超快的,因此,如果您有一個很大的輸入列表並且要執行許多查詢,並且查詢不能很早就短路,這可能會更快。 潛在地增加了一個數量級。

True sum相加的和解是正確的,可能比顯式循環更有效,並且絕對是最簡潔的:

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

但是,它依賴於理解,在像加法這樣的整數上下文中, True計為1False0 您可能不想依靠它。 在這種情況下,您可以重寫它(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位測試我的計算機上長度為250的CPython 3.3.2:

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求和,則取1。 在某些情況下,使用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)

速度快一倍以上,而無需更改代碼。

您可能要研究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

但是,如果您要考慮從listnumpy.array轉換,則numpy不會更快:

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

我會用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)  

如果您擔心“最快”,請用雙端隊列替換此列表:

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

您可以使用條件中內置的sum ,並檢查它是否等於n值。

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

為什么不只使用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()

構建一個生成器,為符合條件的每個項返回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

這有效:

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

它具有只運行一次列表的優點。

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

如果您擔心內存限制,請調整簽名,以便* values是生成器而不是元組

任何尋求“最快解決方案”的人都需要對輸入進行一次遍歷,然后進行一次提前淘汰。

這是解決方案的一個很好的基准起點:

>>> 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 這稱為強度降低

  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速度隔離匹配:

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

一種簡單的方法:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM