web-dev-qa-db-ja.com

発電機出力の長さ

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.を無視して、試してみて間違えたようです。うまくいきます。ご迷惑おかけして申し訳ありません。

116
Maxim

一般的なケースではできないので、それはありません-怠zyな無限ジェネレーターがある場合はどうでしょうか?例えば:

_def fib():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a
_

これは終了することはありませんが、フィボナッチ数を生成します。 next()を呼び出すことにより、必要な数のフィボナッチ数を取得できます。

本当にアイテムの数を知る必要がある場合は、とにかくそれらを一度に線形に反復することはできないので、通常のリストなどの異なるデータ構造を使用してください。

32
Adam Rosenfield

最も簡単な方法は、おそらくsum(1 for _ in gen)です。genはジェネレータです。

223
Matt Dunham
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)
17
mpen

だから、その議論の要約を知りたい人のために。以下を使用して、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))は、最も頻繁に使用されるメモリ消費量の少ないものです。

9
Alex-Bogdanov

reduce(function、iterable [、initializer]) を使用すると、メモリ効率の高い純粋に機能的なソリューションが得られます。

>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
5

more_itertools シンプルなソリューションのパッケージ。例:

>>> import more_itertools

>>> it = iter("abcde")                                         # sample generator
>>> it
<str_iterator at 0x4ab3630>

>>> more_itertools.ilen(it)
5

別の応用例については this post をご覧ください。

3
pylang

定義により、特定の数の引数(定義済みの長さ)の後にジェネレーターのサブセットのみが返され、それでも、これらの有限ジェネレーターのサブセットのみに予測可能な終了があります(ジェネレーターにアクセスすると副作用が発生する可能性があります)より早くジェネレーターを停止できます)。

ジェネレータに長さメソッドを実装する場合は、最初に「長さ」と考えるもの(要素の総数ですか、残りの要素の数ですか)を定義してから、ジェネレータをクラスにラップする必要があります。以下に例を示します。

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]
3
sleblanc

これはハックですが、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で動作し、(私の知る限り)考えられるあらゆる種類の反復可能なものをカバーするはずです。

1
yoniLavi