2011-08-29 14 views
17

Per il problema comune di testo corrispondente tra delimitatori (ad esempio < e >), ci sono due modelli comuni:Testo di corrispondenza tra delimitatori: espressione regolare avida o pigra?

  • utilizzando la avido * o + quantificatore in forma START [^END]* END, ad esempio <[^>]*> o
  • utilizzando il pigro *? o +? quantificatore nel formato START .*? END, ad es. <.*?>.

C'è una ragione particolare per favorire l'uno rispetto all'altro?

risposta

12

Alcuni vantaggi:

[^>]*:

  • più espressivo.
  • Cattura newlines indipendentemente dal flag /s.
  • Considerato più veloce, perché il motore non deve tornare indietro per trovare una corrispondenza riuscita (con il [^>] il motore non effettua le scelte - gli diamo solo un modo per abbinare il modello alla stringa).

.*?

  • No "duplicazione del codice" - il carattere di fine appare solo una volta.
  • Più semplice nei casi in cui il delimitatore di fine è più lungo di un carattere. (una classe di caratteri non funzionerebbe in questo caso) Un'alternativa comune è (?:(?!END).)*. Questo è ancora peggio se il delimitatore END è un altro modello.
+3

Si noti che '[^>] *' sarà solo _non_ backtrack se è seguito da ciò che è all'interno della classe negata ('[^>] *>' in questo caso). Kobi, so che lo sai e probabilmente intendevi questo, ma volevo assicurarmi che gli altri non pensassero che '[^>] *' e '[^>] * +' (possessivo) siano gli stessi. Oltre a ciò, bella risposta! –

+0

@Bart - Buon punto, questa è una scelta scadente di parole. Grazie! – Kobi

7

Il primo è più esplicito, i. e. esclude definitivamente il delimitatore di chiusura dall'essere parte del testo abbinato. Questo non è garantito nel secondo caso (se l'espressione regolare è estesa per corrispondere più di questo tag).

Esempio: Se si tenta di abbinare <tag1><tag2>Hello! con <.*?>Hello!, l'espressione regolare corrisponderà

<tag1><tag2>Hello! 

mentre <[^>]*>Hello! corrisponderà

<tag2>Hello! 
+0

Un bell'esempio che in determinate circostanze la corrispondenza riluttante _can_ corrisponde a due sottostringhe in cui molte persone si aspettano che corrisponda a una sola. –

+0

+1, ottimo esempio. È stato davvero difficile scegliere una risposta questa volta, ma ho preso Kobis, dal momento che elenca i pro ei contro di entrambe le opzioni. – Heinzi

6

Ciò che la maggior parte delle persone non riescono a prendere in considerazione quando si avvicina domande come questo è ciò che succede quando la regex non è in grado di trovare una corrispondenza. Questo è quando è probabile che le buche per le prestazioni dell'assassino siano visualizzate. Ad esempio, prendi l'esempio di Tim, dove stai cercando qualcosa come <tag>Hello!. Considerate ciò che accade con:

<.*?>Hello! 

Il motore regex trova una < e trova rapidamente una chiusura >, ma non >Hello!. Quindi lo .*? continua a cercare > che è seguito da Hello!.Se non ce n'è uno, andrà fino alla fine del documento prima che si arrenda. Quindi il motore regex riprende la scansione finché non trova un altro < e ci riprova. sappiamo già come andrà a finire, ma il motore regex, in genere, non lo fa; passa attraverso lo stesso rigamarole con ogni < nel documento. Consideriamo ora l'altra espressione regolare:

<[^>]*>Hello! 

Come prima, corrisponde rapidamente dal < al >, ma non riesce a corrispondere Hello!. Tornerà indietro allo <, quindi uscirà e inizierà la scansione per un altro <. Controllerà comunque ogni < come la prima espressione regolare, ma non cercherà fino alla fine del documento ogni volta che ne trova una.

Ma è anche peggio di così. Se ci pensi, lo .*? equivale effettivamente a un lookahead negativo. Sta dicendo "Prima di consumare il prossimo personaggio, assicurati che il resto della regex non possa corrispondere in questa posizione." In altre parole,

/<.*?>Hello!/ 

... è equivalente a:

/<(?:(?!>Hello!).)*(?:>Hello!|\z(*FAIL))/ 

Così in ogni posizione che si sta eseguendo, non solo un normale tentativo partita, ma un lookahead molto più costoso. (È ad almeno due volte più costosa, perché il lookahead deve esaminare almeno un carattere, quindi il . va avanti e consuma un carattere.)

((*FAIL) è uno dei backtracking-control verbs (supportati anche in PHP). |\z(*FAIL) mezzi di Perl "o raggiungere la fine del documento e rinunciare".)

Infine, c'è un altro vantaggio dell'approccio di classe carattere negato. Anche se non lo fa (come @Bart sottolineato) agire come il quantificatore è possessivo, non c'è nulla che impedisca di fare è possessiva, se il sapore lo supporta:

/<[^>]*+>Hello!/ 

... o avvolgerlo in un gruppo atomica:

/(?><[^>]*>)Hello!/ 

non solo queste espressioni regolari mai marcia indietro inutilmente, non hanno per salvare le informazioni sullo stato che rende backtracking possibile.

+2

Buona risposta. Un punto piuttosto importante qui, tuttavia, è che il confronto di '<.*?> Hello!' A '<[^>] *> Hello!' Non è abbastanza giusto. Il tuo delimitatore finale in questo caso è in realtà '> Ciao!', Non '>', e '[^>]' non può gestirlo affatto. Ho * provato * a riferirmi nell'ultimo punto della mia risposta. – Kobi

+1

Sì, aggiungendo 'Hello!' Alla regex originale cambia in modo efficace il delimitatore di chiusura da un singolo carattere a una sequenza di più caratteri. E questo trasforma il '. *?'versione in un potenziale buco nero, mentre la versione' [^>] * 'funziona ancora bene. Sto dicendo che, da solo, non c'è praticamente nulla da scegliere tra i due stili; lascia che la regex diventi un po 'più complessa, tuttavia, e la scelta diventa di cruciale importanza. –