2010-07-27 10 views
6

Sto cercando attraverso un certo file di testo per una determinata stringa con il metodo.python - calorosa dimensione iteratore?

re.finditer(pattern,text) Mi piacerebbe sapere quando questo non restituisce nulla. nel senso che non poteva trovare nulla nel testo passato.

So che iteratori callable, hanno next() e __iter__

Vorrei sapere se ho potuto ottenere la dimensione o scoprire se restituisce alcuna stringa corrispondente mio modello.

+3

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

+0

Se si incolla il codice con cui si sta lavorando, potremmo riuscire a trovare risposte migliori. –

risposta

5

EDIT 3: La risposta da @hynekcer è molto molto meglio di questo.

EDIT 2: Questo non funziona se si dispone di un iteratore infinita, o uno che consuma troppo molti gigabyte (nel 2010 1 Gigabyte è ancora una grande quantità di ram/spazio su disco) di RAM/spazio su disco .

Avete già visto una buona risposta, ma qui è un trucco costoso che è possibile utilizzare se si vuole mangiare una torta e averla troppo :) Il trucco è che abbiamo di clonare la torta, e quando si è fatto mangiare, lo rimettiamo nella stessa scatola. Ricorda, quando si itera su iteratore, di solito diventa vuoto, o almeno perde valori restituiti in precedenza.

>>> 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: Ecco una versione più sicura, ma il suo utilizzo richiede ancora un po 'di disciplina. Non sembra abbastanza pitonico. Otterrai la soluzione migliore se pubblichi l'intero campione di codice rilevante che stai tentando di implementare.

>>> 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

Questo non funziona con la maggior parte degli iteratori o dei generatori. 'getIterLength' consumerà il tuo' iteratore'; assegnando 'iter (temp)' a 'iterator' all'interno della funzione crea solo una nuova variabile locale chiamata' iterator' lì che viene scartata al ritorno dalla funzione. Prova a sostituire la riga 'f = xrange (20)' nel tuo esempio con 'f = iter ([1,2,3,4,5])' per vedere cosa intendo. –

+0

Oppure confronta 'id (f)' con 'id (iterator)' all'inizio della funzione (sono gli stessi), 'id (iteratore)' alla fine della funzione (è diverso) e 'id (f) 'al ritorno dalla funzione (è la stessa di prima). Non stai mettendo la torta clonata nella stessa scatola, la stai mettendo in una nuova e la butti via. –

+0

Interessante, tuttavia, che funzioni con 'xrange()'. Sicuramente non funziona con 're.finditer()'. –

5

No, spiacente, gli iteratori non sono destinati a conoscere la lunghezza, ma sanno solo quali sono i prossimi, il che li rende molto efficienti nel passaggio delle raccolte. Sebbene siano più veloci, non consentono l'indicizzazione, compresa la conoscenza della lunghezza di una raccolta.

+1

+1. Gli iteratori non sarebbero 1/5 così utili come sono se fossero stati inchiodati con una certa lunghezza in anticipo. Usa (qualsiasi raccolta) per quello. – delnan

+0

non c'è modo di conoscere la lunghezza se non si scorre l'intera sequenza. Gli iteratori –

+0

sono solo per efficienza e dovrebbero essere generalmente utilizzati se è necessario passare attraverso un'intera raccolta indipendentemente dall'ordine, è sempre più veloce iterare attraverso una matrice o una raccolta con un iteratore piuttosto che incrementare un indice e controllare ciascun indice. –

1

È possibile ottenere il numero di elementi in un iteratore facendo:

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

Iteratori sono iteratori perché non hanno ancora generato la sequenza. Questo codice sopra è fondamentalmente estraendo ogni elemento dall'iteratore fino a quando non vuole fermarsi in un elenco, quindi prendendo la lunghezza di tale array. Qualcosa che sarebbe più efficiente della memoria sarebbe:

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

Un approccio difficile alla per-loop è quello di utilizzare in modo efficace di ridurre per contare gli elementi nella iteratore uno per uno. Questa è effettivamente la stessa cosa del ciclo for:

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

questo ignora sostanzialmente la y passò nelle ridurre e solo aggiunge uno. Inizializza la somma parziale a 0.

0

Una soluzione rapida sarebbe quella di trasformare il tuo iteratore in un elenco e controllare la lunghezza di tale elenco, ma farlo potrebbe essere negativo per la memoria se ci sono troppi risultati.

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

Ecco una soluzione che utilizza meno memoria, perché non salva i risultati intermedi, così come le altre soluzioni che utilizzano "lista":

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

Tutte le altre soluzioni hanno lo svantaggio di consumare molta memoria se il pattern è molto frequente nel testo, come il pattern '[az]'.

Test case:

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

Questa soluzione con sum(1 for ...) utilizza solo circa la memoria per il testo come tale, cioè len(text) byte. Le soluzioni precedenti con list possono utilizzare circa 58 o 110 volte più memoria del necessario. È 580 MB per risp. 32-bit. 1.1 GB per Python 64 bit 2.7.

+0

Questo sembra buono! –

1

Mentre alcuni iteratori potrebbero essere in grado di conoscere la loro lunghezza (ad esempio, sono stati creati da una stringa o da un elenco) la maggior parte non lo fa e non può. re.iter è un buon esempio di uno che non può sapere la sua lunghezza fino a quando non è finito.

Tuttavia, ci sono un paio di diversi modi per migliorare il codice corrente:

  • uso re.search per trovare se ci sono le partite, quindi utilizzare re.finditer per fare il trattamento effettivo; oppure

  • utilizzare un valore sentinella con il ciclo for.

La seconda opzione sembra qualcosa di simile:

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