2010-07-27 6 views
6

Je suis à la recherche dans un certain fichier texte pour une certaine chaîne avec la méthode.python - Taille de l'itérateur appelable?

re.finditer(pattern,text) Je voudrais savoir quand cela retourne rien. ce qui signifie qu'il ne pouvait rien trouver dans le texte passé.

Je sais que itérateurs appelables, ont next() et __iter__

Je voudrais savoir si je pouvais obtenir la taille ou savoir si elle retourne aucune chaîne correspondant à mon modèle.

+3

double possible: http://stackoverflow.com/questions/3345785/getting-number-of-elements-in-an-iterator-in -python/ – Daenyth

+0

Si vous collez le code avec lequel vous travaillez, nous serons peut-être en mesure de trouver de meilleures réponses. –

Répondre

5

EDIT 3: La réponse par @hynekcer est beaucoup mieux que cela.

EDIT 2: Cela ne fonctionnera pas si vous avez un itérateur infini, ou qui consomme trop de Gigaoctets (en 2010 1 Go est encore une grande quantité d'espace de RAM/disque) de RAM/espace disque .

Vous avez déjà vu une bonne réponse, mais voici un hack coûteux que vous pouvez utiliser si vous voulez manger un gâteau et l'avoir aussi :) L'astuce est que nous devons cloner le gâteau, et quand vous êtes fait manger, nous le remettons dans la même boîte. Rappelez-vous que lorsque vous itérez sur l'itérateur, il devient généralement vide ou perd au moins les valeurs précédemment retournées.

>>> def getIterLength(iterator): 
    temp = list(iterator) 
    result = len(temp) 
    iterator = iter(temp) 
    return result 

>>> 
>>> f = xrange(20) 
>>> f 
xrange(20) 
>>> 
>>> x = getIterLength(f) 
>>> x 
20 
>>> f 
xrange(20) 
>>> 

EDIT: Voici une version plus sûre, mais il nécessite encore une certaine discipline. Il ne se sent pas tout à fait Pythonic. Vous obtiendrez la meilleure solution si vous avez publié l'intégralité de l'exemple de code pertinent que vous essayez de mettre en œuvre.

>>> def getIterLenAndIter(iterator): 
    temp = list(iterator) 
    return len(temp), iter(temp) 

>>> f = iter([1,2,3,7,8,9]) 
>>> f 
<listiterator object at 0x02782890> 
>>> l, f = getIterLenAndIter(f) 
>>> 
>>> l 
6 
>>> f 
<listiterator object at 0x02782610> 
>>> 
+0

Cela ne fonctionne pas avec la plupart des itérateurs ou générateurs. 'getIterLength' consommera votre' iterator'; l'affectation de 'iter (temp)' à 'iterator' dans la fonction crée uniquement une nouvelle variable locale appelée' iterator' qui est annulée lors du retour de la fonction. Essayez de substituer la ligne 'f = xrange (20)' dans votre exemple par 'f = iter ([1,2,3,4,5])' pour voir ce que je veux dire. –

+0

Ou compare 'id (f)' avec 'id (itérateur)' au début de la fonction (ils sont identiques), 'id (itérateur)' à la fin de la fonction (c'est différent) et 'id (f) 'au retour de la fonction (c'est pareil qu'auparavant). Vous ne mettez pas le gâteau cloné dans la même boîte, vous le mettez dans un nouveau et le jetez. –

+0

Intéressant, cependant, que cela fonctionne avec 'xrange()'. Cela ne fonctionne certainement pas avec 're.finditer()'. –

5

Nope itérateurs désolé ne sont pas destinés à connaître la longueur qu'ils savent tout ce qui est à côté ce qui les rend très efficaces en passant par des collections. Bien qu'ils soient plus rapides, ils ne permettent pas l'indexation, y compris la connaissance de la longueur d'une collection.

+1

+1. Les itérateurs ne seraient pas 1/5 aussi utiles que s'ils étaient cloués à l'avance. Utilisez (toute collection) pour cela. – delnan

+0

il n'y a aucun moyen de connaître la longueur à moins de parcourir toute la séquence. –

+0

Les itérateurs sont uniquement efficaces et doivent généralement être utilisés si vous devez parcourir une collection entière sans tenir compte de l'ordre, il est toujours plus rapide de parcourir un tableau ou une collection avec un itérateur que d'incrémenter un index et de vérifier chaque index. –

1

Vous pouvez obtenir le nombre d'éléments dans un itérateur en faisant:

len([m for m in re.finditer(pattern, text) ]) 

itérateurs sont itérateurs parce qu'ils ne sont pas encore généré de la séquence. Ce code ci-dessus extrait essentiellement chaque élément de l'itérateur jusqu'à ce qu'il veuille s'arrêter dans une liste, puis en prenant la longueur de ce tableau. Quelque chose qui serait plus efficace de la mémoire serait:

count = 0 
for item in re.finditer(pattern, text): 
    count += 1 

Une approche délicate à la boucle for est d'utiliser pour réduire efficacement compter les éléments dans l'itérateur un par un. C'est effectivement la même chose que la boucle for:

reduce((lambda x, y : x + 1), myiterator, 0) 

Cela ne tient pas fondamentalement le y passé dans la réduction et ajoute un seul. Il initialise la somme cumulée à 0.

0

Une solution rapide serait de transformer votre iterator dans une liste et vérifiez la longueur de cette liste, mais cela peut être mauvais pour la mémoire s'il y a trop de résultats.

matches = list(re.finditer(pattern,text)) 
if matches: 
    do_something() 
print("Found",len(matches),"matches") 
10

Voici une solution qui utilise moins de mémoire, car il ne sauve pas les résultats intermédiaires, comme les autres solutions qui utilisent « liste »:

print sum(1 for _ in re.finditer(pattern, text)) 

Toutes les autres solutions ont l'inconvénient de consommer beaucoup de mémoire si le motif est très fréquent dans le texte, comme le motif '[az]'.

cas de test:

pattern = 'a' 
text = 10240000 * 'a' 

Cette solution utilise sum(1 for ...) environ seulement la mémoire pour le texte en tant que tel, qui est len(text) octets. Les solutions précédentes avec list peuvent utiliser environ 58 ou 110 fois plus de mémoire que nécessaire. C'est 580 Mo pour 32 bits resp. 1,1 Go pour Python 64 bits 2.7.

+0

Cela semble bon! –

1

Bien que certains itérateurs puissent connaître leur longueur (par exemple, ils ont été créés à partir d'une chaîne ou d'une liste), la plupart ne le peuvent pas et ne le peuvent pas. re.iter est un bon exemple de celui qui ne peut pas connaître sa longueur jusqu'à ce qu'il soit fini.

Cependant, il y a deux manières différentes pour améliorer votre code actuel:

  • utilisation re.search pour trouver s'il y a des matches, puis utilisez re.finditer pour faire le traitement proprement dit; ou

  • utiliser une valeur sentinelle avec la boucle for.

La deuxième option ressemble à:

match = empty = object() 
for match in re.finditer(...): 
    # do some stuff 
if match is empty: 
    # there were no matches 
Questions connexes