Comprimento de saída do gerador [duplicado]
Pergunta
Esta questão já tem uma resposta aqui:
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.
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.