2011-03-21 8 views
48

要素自体について気にせずに反復可能な要素の数を求めたいのであれば、それを得るにはどうすればよいでしょうか?今、私はジェネレータ/イテレータのアイテム数をカウントする最短の方法は何ですか?

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

を定義しますが、私はlambdaを理解することは、有害とみなされているに近く、lambda _: 1は確かにかなりではありません。

(このユースケース、すなわちgrep -c、正規表現に一致するテキストファイルの行数をカウントしている。)

+4

(2)で_' 'と衝突し、変数名として' _'使用しないでくださいインタラクティブなインタプリタと(3)共通のgettextエイリアスと衝突します。 –

+4

@Sven:私は未使用の変数に対して常に '_'を使います(PrologとHaskellプログラミングの習慣)。 (1)これを最初に尋ねる理由です。私は(2)と(3)を考慮しなかった、それらを指摘してくれてありがとう! –

+2

duplicated:http://stackoverflow.com/questions/390852/is-there-any-built-in-way-to-get-the-length-of-an-iterable-in-python – tokland

答えて

92

通常の方法は

sum(1 for i in it) 
+1

あなたは 'len (list(it)) ' - または要素が一意である場合は、文字を保存するために' len(set(it)) 'を使います。 – F1Rumors

+6

@ F1Rumorsほとんどの場合、 'len(list(it))'を使うのは問題ありません。しかし、たくさんの要素を生成する怠惰なイテレータがあるときは、それらをすべて数えて同時にメモリに格納したくないので、この答えのコードを使用しないでください。 –

+0

が合意した:答えとして、「最短メモリ」より重要な「最短コード」を前提としていた。 – F1Rumors

5

短い方法がある:

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

個の要素(例:数万以上)のを生成している場合は、それらをリストに入れますパフォーマンスの問題になる可能性があります。しかし、これは、ほとんどの場合、パフォーマンスが問題にならないという考えを簡単に表現したものです。 (len(list(it))異なり)固定メモリオーバーヘッド動作を維持する大きな入力に対してスワップスラッシングと再配置オーバーヘッドを回避するためながら反復可能で、長い(かつ反復可能に短い場合ではない有意義遅い)であってもよい場合sum(1 for i in it)より有意義高速です

+0

私はこれを考えていましたが、大きなテキストファイルを処理することが多いため、パフォーマンスは問題になります。 –

+6

メモリを使い果たしていない限り、このソリューションは実際にはパフォーマンスが優れています。純粋なCコードでループを実行するため、すべてのオブジェクトを生成する必要があります。大きなイテレータであっても、すべてがメモリに収まる限り、これは 'sum(1 for i in it) 'よりも高速です。 –

14

方法:

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

dequecountzipが全てCで実装されている)、それはCPythonの上のCコードのループを実行len(list(it))等。通常、ループごとのバイトコード実行を避けることは、CPythonのパフォーマンスの鍵です。

それは(__length_hint__を提供していない任意の入力イテラブルのために利用可能である可能性が高いではない__length_hint__を使用してlist詐欺、itertools機能は、多くの場合、仕事の特別な動作モードを持っている性能を比較するための公正なテストケースを思い付くために意外に難しいです次の値が要求される前に各ループで返された値が解放されると、より速くなります。dequemaxlen=0となります)。私が使用したテストケースは、Python 3.3のyield fromを使用して、入力を取り、特別itertoolsリターン容器最適化または__length_hint__を欠いCレベル発生を返すジェネレータ関数を作成することであった。

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%になりますn def ilen(it): return len(list(it))、入力の長さに関係なく。入力の最小のために

、セットアップ費用はdeque/zip/count/nextを呼び出すためには、それが33%である長さ0の入力のための私のマシン上def ilen(it): sum(1 for x in it)(約200ナノ秒以上無限に長いこの方法をとる意味単純なsumのアプローチよりも増加します)。しかし、より長い入力の場合は、追加の要素あたり約半分の時間で実行されます。長さ5の入力の場合、コストは同等であり、長さ50-100の範囲のどこかで、実際の作業に比べて初期のオーバーヘッドは目立たない。 sumのアプローチには約2倍の時間がかかります。

基本的には、メモリ使用の問題や入力が制限されたサイズでなく、スピードを重視する場合は、このソリューションを使用してください。入力が制限されて小さくても、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_itertoolsilenツールを実装して、サードパーティのライブラリです。 (1)それは彼らが、これは特別な構文のいくつかの種類だと思う作り、人々を混乱する傾向があるためpip install more_itertools

import more_itertools as mit 


mit.ilen(x for x in range(10)) 
# 10