2011-03-21 34 views
48

如果我想要一個迭代中的項目數量,而不關心這些元素本身,那麼python的方法是什麼?現在,我會定義計算生成器/迭代器中項目數的最簡單方法是什麼?

def ilen(it): 
    return sum(itertools.imap(lambda _: 1, it)) # or just map in Python 3 

但我明白lambda接近被認爲有害的,lambda _: 1肯定是不漂亮。

(這樣做的使用情況進行計數的行數在一個文本文件匹配正則表達式,即grep -c。)

+4

請不要使用'_'作爲變量名,因爲:(1)它往往會迷惑人們,使他們認爲這是某種特殊的語法,(2)與'_'碰撞交互式解釋器和(3)與常見的gettext別名衝突。 – 2011-03-21 22:39:32

+4

@Sven:我一直使用'_'作爲未使用的變量(來自Prolog和Haskell編程的習慣)。 (1)是首先提出這個問題的原因。我沒有考慮(2)和(3),謝謝你指出它們! – 2011-03-21 22:47:04

+2

重複:http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland 2011-03-21 22:47:16

回答

92

通常的方法是

sum(1 for i in it) 
+1

你可以使用'len (list(it))' - 或者如果元素是唯一的,那麼'len(set(it))'保存一個字符。 – F1Rumors 2016-11-14 05:08:10

+6

@ F1Rumors在大多數情況下使用'len(list(it))'很好。然而,當你有一個懶惰的迭代器產生很多很多的元素時,你不希望將它們全部存儲在內存中,只是爲了對它們進行計數,這可以通過使用這個答案中的代碼來避免。 – 2016-11-14 11:11:02

+0

同意:作爲答案,它是以「最短代碼」比「最低存儲量」更重要爲依據的。 – F1Rumors 2016-11-14 15:13:12

5

短的方式是:

def ilen(it): 
    return len(list(it)) 

請注意,如果你正在生成的元素(比如數千甚至更多,幾十)的很多,然後將它們放在一個列表可能會成爲性能問題。然而,這是對大多數情況下性能不起作用的想法的簡單表達。

+0

我想到了這一點,但性能確實很重要,因爲我經常處理大型文本文件。 – 2011-03-21 22:57:05

+6

只要你沒有耗盡內存,這個解決方案實際上在性能方面相當不錯,因爲這將在純C代碼中完成循環 - 所有對象都必須生成。即使對於大型迭代器,只要所有內容都適合內存,這比'sum(1 for i in it)'快。 – 2011-03-21 23:18:47

14

方法這是有意義的速度比sum(1 for i in it)當迭代可以是長(並且當可迭代是短不有意義慢),同時保持固定的存儲器開銷行爲(不像len(list(it))),以避免交換顛簸和再分配開銷較大的輸入:

# On Python 2 only, get zip that lazily generates results instead of returning list 
from future_builtins import zip 

from collections import deque 
from itertools import count 

def ilen(it): 
    # Make a stateful counting iterator 
    cnt = count() 
    # zip it with the input iterator, then drain until input exhausted at C level 
    deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far 
    # Since count 0 based, the next value is the count 
    return next(cnt) 

喜歡len(list(it))它執行上CPython的C代碼環路(dequecountzip在C中的所有實現的);避免每個循環的字節碼執行通常是CPython性能的關鍵。

這是令人驚訝的困難拿出公平的測試用例性能對比(list騙子使用__length_hint__這是不太可能可用於任意輸入iterables,itertools功能,不提供__length_hint__經常有工作的特殊操作模式當每個循環上返回的值在請求下一個值之前釋放釋放時更快,其中dequemaxlen=0將執行)。我使用的測試案例是創造一個發電機的功能,將採取的輸入,並返回一個C水平發生器,缺乏特殊itertools返回容器優化或__length_hint__,使用Python 3.3的yield from

def no_opt_iter(it): 
    yield from it 

然後使用ipython%timeit魔術(100替換不同的常量):

>>> %%timeit -r5 fakeinput = (0,) * 100 
... ilen(no_opt_iter(fakeinput)) 

當輸入不夠大,len(list(it))會導致內存問題,上運行的Python 3.5 x64的Linux中,我的解決方案需要大約長50%THA無論輸入的長度如何,都可以使用def ilen(it): return len(list(it))

對於最小的輸入,設置成本調用deque/zip/count/next意味着需要無限長這種方式比def ilen(it): sum(1 for x in it)(約200納秒更我的機器上用於長度爲0的輸入,這是一個33%的在簡單的sum方法中增加),但對於較長的輸入,其運行時間約爲每增加一個元素的一半;對於長度爲5的輸入,成本是等同的,並且在長度爲50-100的範圍內,與實際工作相比,初始開銷是不明顯的; sum方法需要大約兩倍的時間。

基本上,如果內存使用問題或輸入不具有有限大小,並且您關心速度而不是簡潔性,請使用此解決方案。如果投入是有限的,並且很小,len(list(it))可能是最好的,如果它們是無限的,但簡單/簡潔計數,則可以使用sum(1 for x in it)

1

我喜歡cardinality這個包,它非常輕量級,並且嘗試使用盡可能快的實現,具體取決於迭代。

用法:

>>> import cardinality 
>>> cardinality.count([1, 2, 3]) 
3 
>>> cardinality.count(i for i in range(500)) 
500 
>>> def gen(): 
...  yield 'hello' 
...  yield 'world' 
>>> cardinality.count(gen()) 
2 
1

more_itertools是一個第三方庫,實現了ilen工具。 pip install more_itertools

import more_itertools as mit 


mit.ilen(x for x in range(10)) 
# 10 
相關問題