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.

Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top