web-dev-qa-db-ja.com

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

一般的に、Pythonでイテレータに含まれる要素の数を知るための効率的な方法はありますか?

111
user248237

いいえ、できません。

例:

import random

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

iterator = gen(10)

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

90
Tomasz Wysocki

このコードは動作するはずです:

>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50

各アイテムを反復処理してカウントしますが、これが最も速い方法です。

また、イテレータにアイテムがない場合にも機能します。

>>> sum(1 for _ in range(0))
0
188
John Howard

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

iter_length = len(list(iterable))

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

解決しようとしている実際の問題を教えていただければ、実際の目標を達成するためのより良い方法を見つけるのに役立ちます。

編集:list()を使用すると、イテラブル全体が一度にメモリに読み込まれますが、これは望ましくない場合があります。別の方法は

sum(1 for _ in iterable)

他の人が投稿したように。これにより、メモリに保存されなくなります。

62
Daenyth

できません(特定のイテレータのタイプが、それを可能にする特定のメソッドを実装している場合を除く)。

通常、イテレータを消費することによってのみイテレータ項目をカウントできます。おそらく最も効率的な方法の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に置き換えます)。

26
zuo

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

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

16
badp

これには 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

実際の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
10
Erwin Mayer

イテレータは、ある種のバッファまたはストリームによって読み取られる次のオブジェクトへのポインタを持つ単なるオブジェクトであり、LinkedListのようなもので、繰り返し処理するまで何個あるかはわかりません。イテレータは、インデックス作成を使用する代わりに、参照によって次に何をするかを伝えるだけなので、効率的であることが意図されています(ただし、次のエントリの数を確認する機能は失われます)。

9
Jesus Ramos

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

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

7
Kevin Jacobs

だから、その議論の要約を知りたい人のために。以下を使用して、5000万長のジェネレーター式をカウントするための最終的なトップスコア:

  • len(list(gen))
  • len([_ for _ in gen])
  • sum(1 for _ in gen),
  • ilen(gen)(from 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()

( 'sum、sec'、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))は、最も頻繁に使用されるメモリ消費量の少ないものです。

6
Alex-Bogdanov

簡単なベンチマーク:

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: 35.4 µs per loop
10000 loops, best of 3: 40.2 µs per loop
10000 loops, best of 3: 50.7 µs per loop

つまり単純なcount_iter_itemsがその方法です。

4
Michael

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

最初の方法は、カウントを保存することです-これには、ファイル/データに触れてそれを変更するものが必要です(または、インターフェイスのみを公開するクラスですが、同じものに要約されます)。

もう1つの方法は、繰り返し処理を行い、その大きさを数えることです。

3
Wayne Werner

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

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

0
tom10
def count_iter(iter):
    sum = 0
    for _ in iter: sum += 1
    return sum
0
hasen

一般に、求められていることを行うことはできませんが、繰り返し処理されたアイテムの数afterが繰り返し処理された回数をカウントすることは、依然として有用です。 。そのためには、 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
0
Jason R. Coombs

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

イテレータは、終了するまで何回反復できるかを知りません。これは無限である可能性があるため、無限があなたの答えかもしれません。

0
FCAlive