web-dev-qa-db-fra.com

Comment transformer un objet "groupeur" itertools en liste

J'essaie d'apprendre à utiliser itertools.groupby dans Python et je voulais trouver la taille de chaque groupe de caractères. Au début, j'ai essayé de voir si je pouvais trouver la longueur d'un seul groupe:

from itertools import groupby
len(list(list( groupby("cccccaaaaatttttsssssss") )[0][1]))

et j'obtiendrais 0 à chaque fois.

J'ai fait un peu de recherche et j'ai découvert que d'autres personnes le faisaient de cette façon:

from itertools import groupby
for key,grouper in groupby("cccccaaaaatttttsssssss"):
    print key,len(list(grouper))

Ce qui fonctionne très bien. Ce qui m'embrouille, c'est pourquoi le dernier code fonctionne-t-il, mais pas le premier? Si je voulais obtenir uniquement le nième groupe comme j'essayais de le faire dans mon code d'origine, comment ferais-je?

11
cafemolecular

La raison pour laquelle votre première approche ne fonctionne pas est que les groupes sont "consommés" lorsque vous créez cette liste avec

list(groupby("cccccaaaaatttttsssssss"))

Pour citer de les groupby docs

Le groupe renvoyé est lui-même un itérateur qui partage l'itérable sous-jacent avec groupby(). Étant donné que la source est partagée, lorsque l'objet groupby() est avancé, le groupe précédent n'est plus visible.

Décomposons-le en plusieurs étapes.

from itertools import groupby

a = list(groupby("cccccaaaaatttttsssssss"))
print(a)
b = a[0][1]
print(b)
print('So far, so good')
print(list(b))
print('What?!')

sortie

[('c', <itertools._grouper object at 0xb715104c>), ('a', <itertools._grouper object at 0xb715108c>), ('t', <itertools._grouper object at 0xb71510cc>), ('s', <itertools._grouper object at 0xb715110c>)]
<itertools._grouper object at 0xb715104c>
So far, so good
[]
What?!

Notre itertools._grouper object at 0xb715104c Est vide car il partage son contenu avec l'itérateur "parent" renvoyé par groupby, et ces éléments ont maintenant disparu parce que le premier appel list a itéré sur le parent.

Ce n'est vraiment pas différent de ce qui se passe si vous essayez d'itérer deux fois sur n'importe quel itérateur, par exemple une simple expression de générateur.

g = (c for c in 'python')
print(list(g))
print(list(g))

sortie

['p', 'y', 't', 'h', 'o', 'n']
[]

BTW, voici une autre façon d'obtenir la longueur d'un groupe groupby si vous n'avez pas réellement besoin de son contenu; c'est un peu moins cher (et utilise moins de RAM) que de construire une liste juste pour trouver sa longueur.

from itertools import groupby

for k, g in groupby("cccccaaaaatttttsssssss"):
    print(k, sum(1 for _ in g))

sortie

c 5
a 5
t 5
s 7
13
PM 2Ring