Pythonは、熱心なイテレート可能な長さを取得するための素敵なメソッドlen(x)
を提供します。しかし、ジェネレーターの内包表記と関数で表される遅延イテラブルについては、似たようなものは見つかりませんでした。もちろん、次のような記述は難しくありません。
_def iterlen(x):
n = 0
try:
while True:
next(x)
n += 1
except StopIteration: pass
return n
_
しかし、私は自転車を再実装しているという感覚を取り除くことはできません。
(関数を入力している間、ある考えが思いつきました。引数を「破壊する」ため、そのような関数は実際にはないかもしれません。しかし、私の場合は問題ではありません)。
追伸:最初の回答について-はい、len(list(x))
のようなものも機能しますが、メモリの使用量が大幅に増加します。
P.P.S .:再チェック... P.S.を無視して、試してみて間違えたようです。うまくいきます。ご迷惑おかけして申し訳ありません。
一般的なケースではできないので、それはありません-怠zyな無限ジェネレーターがある場合はどうでしょうか?例えば:
_def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
_
これは終了することはありませんが、フィボナッチ数を生成します。 next()
を呼び出すことにより、必要な数のフィボナッチ数を取得できます。
本当にアイテムの数を知る必要がある場合は、とにかくそれらを一度に線形に反復することはできないので、通常のリストなどの異なるデータ構造を使用してください。
最も簡単な方法は、おそらくsum(1 for _ in gen)
です。genはジェネレータです。
def count(iter):
return sum(1 for _ in iter)
またはさらに良い:
def count(iter):
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
反復可能でない場合、TypeError
をスローします。
または、ジェネレータで特定の何かをカウントする場合:
def count(iter, key=None):
if key:
if callable(key):
return sum(bool(key(x)) for x in iter)
return sum(x == key for x in iter)
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
だから、その議論の要約を知りたい人のために。以下を使用して、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)
、実行のパフォーマンス(メモリ消費を含む)でソートすると、驚くでしょう:
`` `
_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()
_
( 'sum、sec'、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))
は、最も頻繁に使用されるメモリ消費量の少ないものです。
reduce(function、iterable [、initializer]) を使用すると、メモリ効率の高い純粋に機能的なソリューションが得られます。
>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
more_itertools
シンプルなソリューションのパッケージ。例:
>>> import more_itertools
>>> it = iter("abcde") # sample generator
>>> it
<str_iterator at 0x4ab3630>
>>> more_itertools.ilen(it)
5
別の応用例については this post をご覧ください。
定義により、特定の数の引数(定義済みの長さ)の後にジェネレーターのサブセットのみが返され、それでも、これらの有限ジェネレーターのサブセットのみに予測可能な終了があります(ジェネレーターにアクセスすると副作用が発生する可能性があります)より早くジェネレーターを停止できます)。
ジェネレータに長さメソッドを実装する場合は、最初に「長さ」と考えるもの(要素の総数ですか、残りの要素の数ですか)を定義してから、ジェネレータをクラスにラップする必要があります。以下に例を示します。
class MyFib(object):
"""
A class iterator that iterates through values of the
Fibonacci sequence, until, optionally, a maximum length is reached.
"""
def __init__(self, length):
self._length = length
self._i = 0
def __iter__(self):
a, b = 0, 1
while not self._length or self._i < self._length:
a, b = b, a + b
self._i += 1
yield a
def __len__(self):
"This method returns the total number of elements"
if self._length:
return self._length
else:
raise NotImplementedError("Infinite sequence has no length")
# or simply return None / 0 depending
# on implementation
使用方法は次のとおりです。
In [151]: mf = MyFib(20)
In [152]: len(mf)
Out[152]: 20
In [153]: l = [n for n in mf]
In [154]: len(l)
Out[154]: 20
In [155]: l
Out[155]:
[1,
1,
2,
...
6765]
In [156]: mf0 = MyFib(0)
In [157]: len(mf0)
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
<ipython-input-157-2e89b32ad3e4> in <module>()
----> 1 len(mf0)
/tmp/ipython_edit_TWcV1I.py in __len__(self)
22 return self._length
23 else:
---> 24 raise NotImplementedError
25 # or simply return None / 0 depending
26 # on implementation
NotImplementedError:
In [158]: g = iter(mf0)
In [159]: l0 = [g.next(), g.next(), g.next()]
In [160]: l0
Out[160]: [1, 1, 2]
これはハックですが、len
を一般的な反復可能オブジェクト(その方法で消費する)で本当に動作させたい場合は、len
の独自のバージョンを作成できます。
len
関数は、基本的に以下と同等です(ただし、実装では通常、余分なルックアップを回避するための最適化が提供されます)。
def len(iterable):
return iterable.__len__()
したがって、new_len
試してみて、__len__
は存在しません。反復可能要素を消費して、要素の数を数えます:
def new_len(iterable):
try:
return iterable.__len__()
except AttributeError:
return sum(1 for _ in iterable)
上記はPython 2/3で動作し、(私の知る限り)考えられるあらゆる種類の反復可能なものをカバーするはずです。