Pythonのイテレータの要素数を取得する


回答:


101

いいえ、できません。

例:

import random

def gen(n):
    for i in xrange(n):
        if random.randint(0, 1) == 0:
            yield i

iterator = gen(10)

iteratorを反復するまでの長さは不明です。


14
または、def gen(): yield random.randint(0, 1)は無限であるため、反復することによって長さを見つけることはできません。
tgray 2010

1
したがって、明らかなことを検証するには、イテレータの「サイズ」を取得する最良の方法は、反復を行った回数を数えることですよね。この場合、それはnumIters = 0 ; while iterator: numIters +=1
マイクウィリアムソン

興味深いので、それが停止の問題です
赤羽バ

231

このコードは機能するはずです:

>>> 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イテレーター設計の避けられない結果です。要素を保持したい場合は、要素をリストなどに格納する必要があります。


10
これは、OPが実行したくないことを正確に実行しているように見えます。イテレータを反復処理してカウントします。
Adam Crossland、

36
これは、イテラブルの要素を数えるスペース効率の良い方法です
キャプテンレプトン

9
これはOPが望んでいることではありませんが、彼の質問に答えがないことを考えると、この答えはリストのインスタンス化を回避し、上記のreduceメソッドよりも定数によって経験的に高速です。
Phillip Nordwall、2012

5
仕方ない:_Perlへの参照$_ですか?:)
Alois Mahdal

17
@AloisMahdalいいえ。Pythonでは通常、_値を気にしないダミー変数の名前を使用します。
Taymon

67

いいえ、どの方法でもすべての結果を解決する必要があります。できるよ

iter_length = len(list(iterable))

しかし、無限イテレータでそれを実行しても、もちろん戻ることはありません。また、イテレータを使用するため、コンテンツを使用する場合はリセットする必要があります。

あなたが解決しようとしている実際の問題を私たちに伝えることは、あなたの実際の目標を達成するためのより良い方法を見つけるのに役立つかもしれません。

編集:を使用list()すると、反復可能オブジェクト全体が一度にメモリに読み込まれますが、これは望ましくない場合があります。別の方法は

sum(1 for _ in iterable)

別の人が投稿したように。それはそれをメモリに保持することを避けます。


問題は、何百万ものエントリを持つ "pysam"を含むファイルを読んでいることです。Pysamはイテレータを返します。特定の量を計算するには、ファイル内の読み取りの数を知る必要がありますが、それぞれを読み取る必要はありません...それが問題です。

6
私はpysamユーザーではありませんが、おそらく "lazy"ファイルを読み取っています。メモリに大きなファイルを置きたくないので、それは理にかなっています。だからあなたがノーを知らなければならないなら。反復前のレコードの場合、唯一の方法は2つの反復子を作成し、最初の反復子を使用して要素をカウントし、2番目の反復子を使用してファイルを読み取ります。ところで。使用しないでくださいlen(list(iterable))。すべてのデータがメモリに読み込まれます。次を使用できますreduce(lambda x, _: x+1, iterable, 0)。編集:合計付きのZonda333コードも良いです。
Tomasz Wysocki 2010

1
@ user248237:特定の数量を計算するために使用可能なエントリの数を知る必要があるのはなぜですか?それらの固定量を読み取り、その固定量より少ない場合にケースを管理することができます(itesliceを使用して実行するのは非常に簡単です)。すべてのエントリを読む必要がある別の理由はありますか?
クリス、

1
@Tomasz reduceは非推奨であり、Python 3以降ではなくなることに注意してください。
Wilduck

7
@Wilduck:なくなったのではなく、移動しただけですfunctools.reduce
Daenyth

33

あなたはできません(特定のイテレータのタイプがそれを可能にするいくつかの特定のメソッドを実装することを除いて)。

通常、イテレータを使用することによってのみ、イテレータアイテムをカウントできます。おそらく最も効率的な方法の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.izipzip)。


3
+1:との時間比較ではsum(1 for _ in iterator)、これはほぼ2倍の速さでした。
8

1
各アイテムをメモリに読み込んですぐに破棄することにより、イテラブルを消費すると言う方が正確です。
Rockallite

これは、こと(私が見落としている)に注意することが重要だと引数の順序zip事項:あなたが渡した場合zip(counter, iterable)、あなたは実際には1以上反復可能な数よりも買ってあげます!
Kye W Shi

とてもいい答えです。それに恵みを与えるでしょう。
Reut Sharabani

18

ちょっと。メソッドをチェックすることはできます__length_hint__、gsneddersが役立つように指摘しているように、少なくともPython 3.4までは、ドキュメント化されていない実装の詳細スレッド内のメッセージに続く)であり、鼻の悪魔を非常に消滅または召喚できることに注意してください

そうでなければ、いいえ。イテレータは、next()メソッドを公開するだけのオブジェクトです。必要なだけ何度でも呼び出すことができ、最終的にレイズする場合とレイズしない場合がありStopIterationます。幸い、この動作はほとんどの場合、コーダーには透過的です。:)


5
PEP 424およびPython 3.4 では、これは当てはまりません。__length_hint__は現在文書化されていますが、これはヒントであり、正確性を保証するものではありません。
gsnedders 2014

12

私はこのためのカーディナリティパッケージが好きです。これは非常に軽量であり、反復可能オブジェクトに応じて可能な限り最速の実装を使用しようとします。

使用法:

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

その関数を使用すれば、引き続きイテレータを反復できると思いますが、そうですか?
jcollum

12

それで、その議論の要約を知りたい人のために。以下を使用して、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)

実行のパフォーマンス(メモリ消費を含む)でソートすると、驚かれるでしょう。

「」

1:test_list.py:8:0.492 KiB

gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))

( 'list、sec'、1.9684218849870376)

2:test_list_compr.py:8:0.867 KiB

gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])

( 'list_compr、sec'、2.5885991149989422)

3:test_sum.py:8:0.859 KiB

gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()

( '合計、秒'、3.441088170016883)

4:more_itertools / more.py:413:1.266 KiB

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)

5:test_reduce.py:8:0.859 KiB

gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)

( 'reduce、sec'、13.436614598002052) `` `

つまり、len(list(gen))最も頻繁に使用され、メモリ消費量が少ない


どのようにメモリ消費を測定しましたか?
ノルマニウス

1
len(list(gen))削減に基づくアプローチよりも少ないメモリを使用する理由を説明できますか?前者は、listメモリ割り当てを含む新しいものを作成しますが、後者は必要ありません。したがって、後者の方がよりメモリ効率が良いと思います。また、メモリの消費量は要素のタイプによって異なります。
normanius

参考:python 3.6.8(MacBookPro上)では、メソッド1がランタイムに関して他のメソッドよりも優れていることを再現できます(メソッド4はスキップしました)。
ノルマニウス

len(tuple(iterable))さらに効率的になる可能性があります:Nelson Minarによる記事
VMAtm

9

イテレータは、なんらかのバッファまたはストリームによって読み取られる次のオブジェクトへのポインタを持つ単なるオブジェクトです。これは、反復処理を行うまで何が必要かがわからないLinkedListのようなものです。イテレータは、インデックスを使用する代わりに参照によって次に何をするかを通知するだけなので、効率的です(ただし、見てきたとおり、次のエントリの数を確認できません)。


2
イテレータはリンクリストのようなものではありません。イテレータから返されたオブジェクトは次のオブジェクトを指さず、これらのオブジェクトは(必ずしも)メモリに格納されません。むしろ、(格納されたリストに基づいて可能である必要はないが)内部ロジックに基づいて、オブジェクトを次々に生成できます。
トム

1
@Tom私は主にLinkedListを例として使用していましたが、これは、次のもの(あるものがある場合)を知っているだけなので、どれだけ持っているかわからないということです。私の言い回しが少しずれているように見える場合、またはそれらが同じものであると暗示する場合は、お詫び申し上げます。
イエスラモス

8

あなたの元の質問に関しては、答えはまだPythonでイテレーターの長さを知る一般的な方法がないということです。

あなたの質問がpysamライブラリのアプリケーションによって動機付けられているとすれば、私はより具体的な答えを与えることができます:私はPySAMへの貢献者であり、決定的な答えはSAM / BAMファイルは整列された読み取りの正確な数を提供しないということです。この情報は、BAMインデックスファイルから簡単に入手することもできません。最善の方法は、ファイルの合計サイズに基づいていくつかのアラインメントを読み取って外挿した後、ファイルポインターの位置を使用してアラインメントのおおよその数を推定することです。これはプログレスバーを実装するのに十分ですが、一定の時間でアライメントをカウントする方法ではありません。


6

簡単なベンチマーク:

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)

注:このテストはpython2に基づいています
normanius

3

コンピュータで「何か」の長さを取得するには、2つの方法があります。

最初の方法は、カウントを保存することです。これには、ファイル/データに手を加えて変更する必要があります(またはインターフェースのみを公開するクラスですが、結局同じことです)。

もう1つの方法は、それを反復して、その大きさを数えることです。


0

このタイプの情報をファイルヘッダーに入れて、pysamがこれにアクセスできるようにするのが一般的な方法です。形式はわかりませんが、APIを確認しましたか?

他の人が言ったように、あなたはイテレータから長さを知ることができません。


0

これは、オブジェクトへのポインタであるイテレータの定義そのものと、次のオブジェクトに到達する方法に関する情報に反しています。

イテレータは、終了するまで何回繰り返すことができるかわかりません。これは無限かもしれないので、無限大があなたの答えかもしれません。


それは何にも違反しておらず、反復子を使用するときに事前知識を適用することに問題はありません。要素の数が限られていることを知っているところでは、何十億ものイテレータがあります。単にリストをフィルタリングすることを考えてください。最大長を簡単に指定できます。実際にフィルター条件に適合する要素の数がわからないだけです。一致する要素の数を知りたいのは有効なアプリケーションであり、イテレータの神秘的なアイデアに違反するものではありません。
マイケル

0

一般に、要求された操作を実行することはできませんが、繰り返し処理された、繰り返し処理されアイテムの数を数えると役立つ場合があります。そのためには、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


-1

おそらく、イテレータが使い果たされないように、反復なしでアイテムの数をカウントし、後で再び使用する必要があります。これは、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

1
範囲はイテレータではありません。コピーできるイテレータタイプがいくつかありますが、他のタイプではこのコードがTypeErrorで失敗する原因となります(たとえば、ジェネレーター)。map結果の関数呼び出しが1回だけ行われることを期待して反復子を返しました。
user2357112は2
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.