Pergunta

Python fornece um método agradável para obter o comprimento de um ansioso iterable, len(x) que é. Mas eu não consegui encontrar nada semelhante para iterables preguiçosos representados por compreensões e funções de gerador. Claro, não é difícil escrever algo como:

def iterlen(x):
  n = 0
  try:
    while True:
      next(x)
      n += 1
  except StopIteration: pass
  return n

Mas eu não posso livrar-se de um sentimento que eu estou reimplementar uma bicicleta.

(Enquanto eu estava escrevendo a função, um pensamento atingiu minha mente: talvez não há realmente nenhuma tal função, porque "destrói" o seu argumento Não é um problema para o meu caso, no entanto.)

.

P.S .: sobre as primeiras respostas -. Sim, algo como len(list(x)) iria trabalhar muito, mas que aumenta drasticamente o uso de memória

P.P.S .: re-marcada ... Desconsidere o P. S., parece que cometi um erro ao tentar isso, ele funciona bem. Desculpem o problema.

Foi útil?

Solução

Não há um porque você não pode fazê-lo no caso geral - e se você tem um gerador infinito preguiçoso? Por exemplo:

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

Isso nunca termina, mas irá gerar os números de Fibonacci. Você pode obter o maior número de números de Fibonacci como você deseja chamando next().

Se você realmente precisa saber o número de itens existem, então você não pode iterate através deles linearmente um tempo de qualquer maneira, então é só usar uma estrutura de dados diferente, como uma lista regular.

Outras dicas

A maneira mais fácil é provavelmente apenas sum(1 for _ in gen) onde gen é o seu gerador.

def count(iter):
    return sum(1 for _ in iter)

Ou ainda melhor:

def count(iter):
    try:
        return len(iter)
    except TypeError:
        return sum(1 for _ in iter)

Se não é iterable, ele vai jogar um TypeError.

Ou, se você quiser contar algo específico no gerador:

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)

Assim, para aqueles que gostariam de saber o resumo dessa discussão. As melhores pontuações finais para contar uma expressão de 50 milhões de-lengthed gerador usando:

  • len(list(gen)),
  • len([_ for _ in gen]),
  • sum(1 for _ in gen),
  • ilen(gen) (de more_itertool ),
  • reduce(lambda c, i: c + 1, gen, 0),

classificadas pelo desempenho de execução (incluindo o consumo de memória), vai fazer você surpreso:

`` `

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, seg', 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, seg', 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)

( 'reduzir, sec', 13,436614598002052) `` `

Assim, len(list(gen)) é a memória mais frequente e menos consumo

Você pode usar enumerate () para percorrer o fluxo de dados gerado, em seguida, retornar o último número -. O número de itens

Eu tentei usar itertools.count () com itertools.izip (), mas sem sorte. Esta é a melhor resposta / mais curta que eu vim acima com:

#!/usr/bin/python

import itertools

def func():
    for i in 'yummy beer':
        yield i

def icount(ifunc):
    size = -1 # for the case of an empty iterator
    for size, _ in enumerate(ifunc()):
        pass
    return size + 1

print list(func())
print 'icount', icount(func)

# ['y', 'u', 'm', 'm', 'y', ' ', 'b', 'e', 'e', 'r']
# icount 10

A solução de Kamil Kisiel é muito melhor:

def count_iterable(i):
    return sum(1 for e in i)

Use reduzir (função, iterable [, initializer]) para uma solução puramente funcional eficiente de memória:

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

Por definição, apenas um subconjunto de geradores retornará depois de um certo número de argumentos (ter um comprimento pré-definido) e, mesmo assim, apenas um subconjunto destes geradores finitos tem uma extremidade previsível (acesso ao gerador pode ter lado -Efeitos que poderia parar o gerador mais cedo).

Se você deseja implementar métodos de comprimento para o seu gerador, você tem que primeiro definir o que se considera o "comprimento" (é o número total de elementos? O número de elementos restantes?), Em seguida, enrole o seu gerador em uma classe . Aqui está um exemplo:

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

Aqui está como usá-lo:

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]

Tente o href="http://more-itertools.readthedocs.io/en/latest/api.html" rel="nofollow noreferrer"> pacote more_itertools para uma solução simples. Exemplo:

>>> import more_itertools

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

>>> more_itertools.ilen(it)
5

este post para outro exemplo aplicada.

Este é um hack, mas se você realmente quer ter um trabalho len em geral iterable (consumi-lo no caminho), você pode criar sua própria versão do len.

A função len é essencialmente equivalente à que se segue (embora implementações geralmente fornecem algumas optimizações para evitar a pesquisa adicional):

def len(iterable):
    return iterable.__len__()

Portanto, podemos definir o nosso new_len para tentar isso, e se __len__ não existe, contar o número de elementos nos por consumir o iterable:

def new_len(iterable):
    try:
      return iterable.__len__()
    except AttributeError:
      return sum(1 for _ in iterable)

As obras acima em Python 2/3, e (tanto quanto eu sei) deve cobrir todo tipo concebível de iterable.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top