一般に、Pythonのイテレータに含まれる要素の数を、各要素を繰り返し処理してカウントすることなく知るための効率的な方法はありますか?
一般に、Pythonのイテレータに含まれる要素の数を、各要素を繰り返し処理してカウントすることなく知るための効率的な方法はありますか?
回答:
いいえ、できません。
例:
import random
def gen(n):
for i in xrange(n):
if random.randint(0, 1) == 0:
yield i
iterator = gen(10)
iterator
を反復するまでの長さは不明です。
def gen(): yield random.randint(0, 1)
は無限であるため、反復することによって長さを見つけることはできません。
numIters = 0 ; while iterator: numIters +=1
?
このコードは機能するはずです:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
各項目を繰り返し処理してカウントしますが、これが最も高速な方法です。
イテレータにアイテムがない場合にも機能します。
>>> sum(1 for _ in range(0))
0
もちろん、無限の入力に対しては永久に実行されるので、イテレータは無限になる可能性があることに注意してください。
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
また、これを行うとイテレータが使い果たされ、それをさらに使用しようとしても要素が表示されないことに注意してください。これは、Pythonイテレーター設計の避けられない結果です。要素を保持したい場合は、要素をリストなどに格納する必要があります。
_
Perlへの参照$_
ですか?:)
_
値を気にしないダミー変数の名前を使用します。
いいえ、どの方法でもすべての結果を解決する必要があります。できるよ
iter_length = len(list(iterable))
しかし、無限イテレータでそれを実行しても、もちろん戻ることはありません。また、イテレータを使用するため、コンテンツを使用する場合はリセットする必要があります。
あなたが解決しようとしている実際の問題を私たちに伝えることは、あなたの実際の目標を達成するためのより良い方法を見つけるのに役立つかもしれません。
編集:を使用list()
すると、反復可能オブジェクト全体が一度にメモリに読み込まれますが、これは望ましくない場合があります。別の方法は
sum(1 for _ in iterable)
別の人が投稿したように。それはそれをメモリに保持することを避けます。
len(list(iterable))
。すべてのデータがメモリに読み込まれます。次を使用できますreduce(lambda x, _: x+1, iterable, 0)
。編集:合計付きのZonda333コードも良いです。
functools.reduce
あなたはできません(特定のイテレータのタイプがそれを可能にするいくつかの特定のメソッドを実装することを除いて)。
通常、イテレータを使用することによってのみ、イテレータアイテムをカウントできます。おそらく最も効率的な方法の1つ:
import itertools
from collections import deque
def count_iter_items(iterable):
"""
Consume an iterable not reading it into memory; return the number of items.
"""
counter = itertools.count()
deque(itertools.izip(iterable, counter), maxlen=0) # (consume at C speed)
return next(counter)
(Pythonは置き換え3.xのitertools.izip
でzip
)。
sum(1 for _ in iterator)
、これはほぼ2倍の速さでした。
zip
事項:あなたが渡した場合zip(counter, iterable)
、あなたは実際には1以上反復可能な数よりも買ってあげます!
ちょっと。メソッドをチェックすることはできますが__length_hint__
、gsneddersが役立つように指摘しているように、少なくともPython 3.4までは、ドキュメント化されていない実装の詳細(スレッド内のメッセージに続く)であり、鼻の悪魔を非常に消滅または召喚できることに注意してください。
そうでなければ、いいえ。イテレータは、next()
メソッドを公開するだけのオブジェクトです。必要なだけ何度でも呼び出すことができ、最終的にレイズする場合とレイズしない場合がありStopIteration
ます。幸い、この動作はほとんどの場合、コーダーには透過的です。:)
私はこのためのカーディナリティパッケージが好きです。これは非常に軽量であり、反復可能オブジェクトに応じて可能な限り最速の実装を使用しようとします。
使用法:
>>> 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
実際のcount()
実装は次のとおりです。
def count(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
それで、その議論の要約を知りたい人のために。以下を使用して、5,000万長のジェネレータ式をカウントするための最終的なトップスコア:
len(list(gen))
、 len([_ for _ in gen])
、 sum(1 for _ in gen),
ilen(gen)
(more_itertoolから)、reduce(lambda c, i: c + 1, gen, 0)
、 実行のパフォーマンス(メモリ消費を含む)でソートすると、驚かれるでしょう。
「」
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
( 'list、sec'、1.9684218849870376)
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
( 'list_compr、sec'、2.5885991149989422)
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
( '合計、秒'、3.441088170016883)
d = deque(enumerate(iterable, 1), maxlen=1)
test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
( 'ilen、sec'、9.812256851990242)
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
( 'reduce、sec'、13.436614598002052) `` `
つまり、len(list(gen))
最も頻繁に使用され、メモリ消費量が少ない
len(list(gen))
削減に基づくアプローチよりも少ないメモリを使用する理由を説明できますか?前者は、list
メモリ割り当てを含む新しいものを作成しますが、後者は必要ありません。したがって、後者の方がよりメモリ効率が良いと思います。また、メモリの消費量は要素のタイプによって異なります。
len(tuple(iterable))
さらに効率的になる可能性があります:Nelson Minarによる記事
イテレータは、なんらかのバッファまたはストリームによって読み取られる次のオブジェクトへのポインタを持つ単なるオブジェクトです。これは、反復処理を行うまで何が必要かがわからないLinkedListのようなものです。イテレータは、インデックスを使用する代わりに参照によって次に何をするかを通知するだけなので、効率的です(ただし、見てきたとおり、次のエントリの数を確認できません)。
あなたの元の質問に関しては、答えはまだPythonでイテレーターの長さを知る一般的な方法がないということです。
あなたの質問がpysamライブラリのアプリケーションによって動機付けられているとすれば、私はより具体的な答えを与えることができます:私はPySAMへの貢献者であり、決定的な答えはSAM / BAMファイルは整列された読み取りの正確な数を提供しないということです。この情報は、BAMインデックスファイルから簡単に入手することもできません。最善の方法は、ファイルの合計サイズに基づいていくつかのアラインメントを読み取って外挿した後、ファイルポインターの位置を使用してアラインメントのおおよその数を推定することです。これはプログレスバーを実装するのに十分ですが、一定の時間でアライメントをカウントする方法ではありません。
簡単なベンチマーク:
import collections
import itertools
def count_iter_items(iterable):
counter = itertools.count()
collections.deque(itertools.izip(iterable, counter), maxlen=0)
return next(counter)
def count_lencheck(iterable):
if hasattr(iterable, '__len__'):
return len(iterable)
d = collections.deque(enumerate(iterable, 1), maxlen=1)
return d[0][0] if d else 0
def count_sum(iterable):
return sum(1 for _ in iterable)
iter = lambda y: (x for x in xrange(y))
%timeit count_iter_items(iter(1000))
%timeit count_lencheck(iter(1000))
%timeit count_sum(iter(1000))
結果:
10000 loops, best of 3: 37.2 µs per loop
10000 loops, best of 3: 47.6 µs per loop
10000 loops, best of 3: 61 µs per loop
つまり、単純なcount_iter_itemsが進むべき道です。
これをpython3用に調整します。
61.9 µs ± 275 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.4 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
82.6 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
これは、オブジェクトへのポインタであるイテレータの定義そのものと、次のオブジェクトに到達する方法に関する情報に反しています。
イテレータは、終了するまで何回繰り返すことができるかわかりません。これは無限かもしれないので、無限大があなたの答えかもしれません。
一般に、要求された操作を実行することはできませんが、繰り返し処理された後、繰り返し処理されたアイテムの数を数えると役立つ場合があります。そのためには、jaraco.itertools.Counterなどを使用できます。これは、Python 3とrwtを使用してパッケージをロードする例です。
$ rwt -q jaraco.itertools -- -q
>>> import jaraco.itertools
>>> items = jaraco.itertools.Counter(range(100))
>>> _ = list(counted)
>>> items.count
100
>>> import random
>>> def gen(n):
... for i in range(n):
... if random.randint(0, 1) == 0:
... yield i
...
>>> items = jaraco.itertools.Counter(gen(100))
>>> _ = list(counted)
>>> items.count
48
おそらく、イテレータが使い果たされないように、反復なしでアイテムの数をカウントし、後で再び使用する必要があります。これは、copy
またはdeepcopy
import copy
def get_iter_len(iterator):
return sum(1 for _ in copy.copy(iterator))
###############################################
iterator = range(0, 10)
print(get_iter_len(iterator))
if len(tuple(iterator)) > 1:
print("Finding the length did not exhaust the iterator!")
else:
print("oh no! it's all gone")
出力は "Finding the length did not exhaust the iterator!
"です
必要に応じて(そしてお勧めできませんが)、len
次のように組み込み関数をシャドウできます。
import copy
def len(obj, *, len=len):
try:
if hasattr(obj, "__len__"):
r = len(obj)
elif hasattr(obj, "__next__"):
r = sum(1 for _ in copy.copy(obj))
else:
r = len(obj)
finally:
pass
return r
map
結果の関数呼び出しが1回だけ行われることを期待して反復子を返しました。