Ho già assegnato un +1 a Mark Byers, ma per quanto mi ricordo il documento non dice molto su come l'abbinamento di espressioni regolari funzioni oltre a spiegare perché un algoritmo è cattivo e un altro molto meglio. Forse qualcosa nei link?
Mi concentrerò sull'ottimale approccio: la creazione di automi finiti. Se ti limiti ad automi deterministici senza minimizzazione, questo non è davvero troppo difficile.
Quello che descriverò (molto rapidamente) è l'approccio adottato in Modern Compiler Design.
Immaginate di avere la seguente espressione regolare ...
a (b c)* d
Le lettere rappresentano caratteri letterali da abbinare. La * è la solita corrispondenza a zero o più ripetizioni. L'idea di base è derivare gli stati in base a regole tratteggiate. Stato zero ci prenderemo come lo stato in cui nulla è stato ancora abbinato, quindi il punto va al fronte ...
0 : .a (b c)* d
L'unica partita possibile è 'a', in modo che il prossimo stato deriviamo è. ..
1 : a.(b c)* d
ora abbiamo due possibilità - corrispondere al 'b' (se c'è almeno una ripetizione di 'b c') o abbinare la 'd' altro. Nota: stiamo fondamentalmente facendo una ricerca con il digramma qui (prima o dopo la profondità prima o qualsiasi altra cosa) ma stiamo scoprendo il digrafo mentre lo cerchiamo. Supponendo una strategia di ampiezza, avremo bisogno di accodare uno dei nostri casi per una considerazione successiva, ma ignorerò il problema da qui in poi. In ogni caso, abbiamo scoperto due nuovi stati ...
2 : a (b.c)* d
3 : a (b c)* d.
Stato 3 è uno stato finale (ci possono essere più di uno). Per lo stato 2, possiamo solo abbinare la "c", ma dobbiamo fare attenzione con la posizione del punto in seguito. Otteniamo "a. (B c) * d" - che è lo stesso di stato 1, quindi non abbiamo bisogno di un nuovo stato.
IIRC, l'approccio in Modern Compiler Design è quello di tradurre una regola quando si colpisce un operatore, al fine di semplificare la gestione del punto. Stato 1 sarebbe stato trasformato da ...
1 : a.b c (b c)* d
a.d
Cioè, la vostra prossima opzione è sia per abbinare la prima ripetizione o per saltare la ripetizione. Gli stati successivi di questo sono equivalenti agli stati 2 e 3. Un vantaggio di questo approccio è che puoi scartare tutte le tue partite passate (tutto prima del ".") Come ti interessa solo le partite future. Questo in genere dà un modello di stato più piccolo (ma non necessariamente uno minimo).
EDIT Se si scartano i dettagli già abbinati, la descrizione dello stato è una rappresentazione dell'insieme di stringhe che possono verificarsi da questo momento in poi.
In termini di algebra astratta, questo è un tipo di chiusura del set. Un'algebra è fondamentalmente un insieme con uno (o più) operatori. Il nostro set è di descrizioni di stato e i nostri operatori sono le nostre transizioni (corrispondenze di personaggi). Un insieme chiuso è uno in cui l'applicazione di qualsiasi operatore a qualsiasi membro nell'insieme produce sempre un altro membro che si trova nell'insieme. La chiusura di un set è il set più grande e mimato che è chiuso. Quindi, fondamentalmente, iniziando con lo stato di partenza ovvio, stiamo costruendo il set minimo di stati che è chiuso rispetto al nostro set di operatori di transizione - il set minimo di stati raggiungibili.
Minimo qui si riferisce al processo di chiusura - potrebbe esserci un automa equivalente più piccolo che viene normalmente definito come minimo.
Con questa idea di base in mente, non è troppo difficile dire "se ho due macchine di stato che rappresentano due serie di stringhe, come posso ricavarne una terza che rappresenta l'unione" (o intersezione, o imposta differenza ...). Invece di regole tratteggiate, le rappresentazioni di stato saranno uno stato corrente (o un insieme di stati correnti) da ciascun automa di input e forse ulteriori dettagli.
Se le tue grammatiche regolari diventano complesse, puoi ridurle al minimo. L'idea di base qui è relativamente semplice. Raggruppate tutti i vostri stati in una classe di equivalenza o "blocco". Quindi verifichi ripetutamente se hai bisogno di dividere i blocchi (gli stati non sono realmente equivalenti) rispetto a un particolare tipo di transizione. Se tutti gli stati di un determinato blocco accettano una corrispondenza dello stesso carattere e, nel farlo, raggiungono lo stesso blocco successivo, sono equivalenti.
L'algoritmo di Hopcrofts è un modo efficace per gestire questa idea di base.
Una cosa particolarmente interessante della minimizzazione è che ogni automa finito deterministico ha precisamente una forma minima. Inoltre, l'algoritmo di Hopcrofts produrrà la stessa rappresentazione di quella forma minimale, indipendentemente dalla rappresentazione di quale caso più ampio ha iniziato. Cioè, questa è una rappresentazione "canonica" che può essere usata per derivare un hash o per ordinamenti arbitrari ma coerenti. Ciò significa che puoi usare gli automi minimi come chiavi nei contenitori.
Quanto sopra è probabilmente una definizione di WRT un po 'sciatta, quindi assicuratevi di cercare da soli i termini prima di usarli voi stessi, ma con un po' di fortuna questo fornisce una rapida introduzione alle idee di base.
BTW - dai uno sguardo al resto di Dick Grunes site - ha un libro in formato PDF gratuito sulle tecniche di analisi. La prima edizione di Modern Compiler Design è abbastanza buona, ma come vedrete, c'è una seconda edizione imminente.
+1 Pensiero interessante. Sarai un esperto in regex se ottieni questo;) –
[interessante] (http://www.codeproject.com/KB/recipes/OwnRegExpressionsParser.aspx#Seven) articolo su come creare un parser RE semplificato (non Python related though) – systempuntoout
http://perl.plover.com/Regex/article.html è una spiegazione dei motori regex che utilizzano gli automi. Potresti anche considerare un progetto più semplice che è stato sollevato qui qualche tempo fa, che è quello di scrivere un traduttore regex-to-English. Ad esempio, '(foo | bar) (baz) +' dovrebbe tradurre in 'o" foo "o bar" quindi uno o più "baz" '. Pyparsing (http://pyparsing.wikispaces.com/Documentation) potrebbe aiutare – katrielalex