Longueur de la sortie du générateur [dupliquer]
Question
Cette question a déjà une réponse ici:
Python fournit une méthode agréable pour obtenir la longueur d'une hâte itératives, len(x)
qui est. Mais je ne pouvais pas trouver quelque chose de semblable pour iterables paresseux représentés par compréhensions et fonctions générateur. Bien sûr, il est difficile d'écrire quelque chose comme:
def iterlen(x):
n = 0
try:
while True:
next(x)
n += 1
except StopIteration: pass
return n
Mais je ne peux pas me débarrasser d'un sentiment que je réimplémentant une bicyclette.
(Bien que je tapais la fonction, une pensée a frappé mon esprit: peut-être il n'y a vraiment pas une telle fonction, parce qu'elle « détruit » son argument pas un problème pour mon cas, cependant.)
. P.S .: en ce qui concerne les premières réponses - oui, quelque chose comme len(list(x))
fonctionnerait aussi, mais qui augmente considérablement l'utilisation de la mémoire
P.P.S .: revérifiés ... Défier le post-scriptum, semble avoir fait une erreur en essayant que cela fonctionne très bien. Désolé pour la peine.
La solution
Il n'y a pas un parce que vous ne pouvez pas le faire dans le cas général - si vous avez un générateur infini paresseux? Par exemple:
def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
ne se termine, mais génère les nombres de Fibonacci. Vous pouvez obtenir autant de nombres de Fibonacci que vous voulez en appelant next()
.
Si vous avez vraiment besoin de connaître le nombre d'éléments il y a, vous ne pouvez pas itérer les linéaire une fois de toute façon, il suffit d'utiliser une structure de données différentes, comme une liste régulière.
Autres conseils
Le plus simple est probablement sum(1 for _ in gen)
où gen est votre générateur.
def count(iter):
return sum(1 for _ in iter)
Ou mieux encore:
def count(iter):
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
Si ce n'est pas itérable, il va lancer une TypeError
.
Ou, si vous voulez compter quelque chose de spécifique dans le générateur:
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)
Donc, pour ceux qui voudraient connaître le résumé de cette discussion. Les meilleurs scores finaux pour compter une expression de générateur 50 millions d'lengthed en utilisant:
-
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)
,
trié par la performance d'exécution (y compris la consommation de mémoire), vous fera surpris:
`` `
1: test_list.py:8: 0,492 KiB
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
( 'liste, s', 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()
( 'somme, 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)
( 'réduire, sec', 13,436614598002052) `` `
Alors, len(list(gen))
est le plus fréquent et moins consommables mémoire
Vous pouvez utiliser enumerate () en boucle à travers le flux de données généré, puis retourner le dernier numéro -. Le nombre d'éléments
J'ai essayé d'utiliser itertools.count () avec itertools.izip () mais pas de chance. Ceci est la meilleure / la plus courte réponse que je suis venu avec:
#!/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
La solution de Kamil Kisiel est bien meilleur:
def count_iterable(i):
return sum(1 for e in i)
Utilisez réduire (fonction, iterable [, initialiseur]) une mémoire efficace solution purement fonctionnelle:
>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
Par définition, seul un sous-ensemble des générateurs sera de retour après un certain nombre d'arguments (avoir une longueur prédéfinie), et même alors, seulement un sous-ensemble de ces générateurs finis ont une fin prévisible (accéder au générateur peut avoir des côtés -Effets qui pourrait arrêter le générateur avant).
Si vous souhaitez mettre en œuvre des méthodes de longueur pour votre générateur, vous devez d'abord définir ce que vous considérez comme la « longueur » (est-ce le nombre total d'éléments? Le nombre d'éléments restants?), Puis enveloppez votre générateur dans une classe . Voici un exemple:
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
Voici comment l'utiliser:
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]
Essayez le paquet more_itertools
pour une solution simple. Exemple:
>>> import more_itertools
>>> it = iter("abcde") # sample generator
>>> it
<str_iterator at 0x4ab3630>
>>> more_itertools.ilen(it)
5
Voir ce poste à un autre exemple appliqué.
Ceci est un hack, mais si vous voulez vraiment avoir le travail de len
sur une itérables générale (consommer de la manière), vous pouvez créer votre propre version de len
.
La fonction len
est essentiellement équivalente à ce qui suit (bien que les mises en œuvre fournissent des optimisations pour éviter la recherche supplémentaire):
def len(iterable):
return iterable.__len__()
Par conséquent, nous pouvons définir notre new_len
pour essayer, et si __len__
n'existe pas, compter le nombre de nous-mêmes éléments en consommant itérable:
def new_len(iterable):
try:
return iterable.__len__()
except AttributeError:
return sum(1 for _ in iterable)
Les travaux ci-dessus en Python 2/3, et (pour autant que je sache) devrait couvrir tous les types imaginables de itérables.