2012-03-26 18 views
5

Sto cercando un modo rapido in C# per trovare tutte le date in una stringa (la stringa è un testo grande, devo eseguire la scansione di circa 200.000 stringhe diverse).Trovare le date nella stringa

poiché ci sono molti modi di scrivere la data (ad esempio 31/12/2012 o 31 dicembre 2012 e molto altro), Sto usando questo Regex (che dovrebbe coprire quasi tutti i modi frequenti di scrivere le date):

findDates String = ": - | (((\ d {1,4}) /.- /?.):? (\ s \ d {1,2}) \ s + (Jan (?: naio) {0,1} \ {0,1} | febbraio (:.?. braio) {0,1} \ {0,1} | mar (: ch) {0,1} \ {?. 0,1} | aprile (:? il) {0,1} \ {0,1} | può \ {0,1} | giugno: {0,1} \ {0,1 (e?). } | luglio (:? y) {0,1} \ {0,1} | agosto (: Ust?) {0,1} \ {0,1} | settembre (:? tembre) {0,1 } \ {0,1} | ottobre (:? Ober) {0,1} \ {0,1} | novembre (:? brace) {0,1} \ {0,1} | dic (.? : brace) {0,1} \ {0,1}) \ s + (\ d {2,4})) | (:(gennaio (:.?. naio) {0,1} \ {0,1 } | febbraio (:? braio) {0,1} \ {0,1} | marzo: {0,1} \ {0,1} | aprile (ch?). (:? il) {0,1 } \ {0,1} |. può \ {0,1} |. Jun (?: e) {0,1} \ {0,1} | luglio (:.?. y) {0,1} \ {0,1} | agosto (: Ust) {0,1} \ {0,?. 1} | settembre (:? tembre) {0,1} \ {0,1} | ottobre (:? Ober) {0,1} \ {0,1} | novembre (:? brace) {0, 1} \ {0,1} | dec. (:? brace). {0,1} \ {0,1}) \ s + ([0-9] {1,2}) [\ s,] + (\ d {2,4})) ";

con tag "RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace". inoltre, ho provato a pre-compilare la regex per renderlo ancora più veloce.

Il problema è che è molto lento (su alcuni file di testo più di 2 secondi) Esiste un modo migliore ed efficace per farlo?

Grazie

+0

Un semplice commento, ma vorrei provare non regex uno per uno. Prima scansione con la prima espressione regolare ed eliminazione delle parole corrispondenti, quindi eseguire l'altra. Potrebbe essere più veloce a seconda della stringa di input. – daryal

+1

'{0,1}' è uguale a '?'. Il cambiamento non accelera, ma semplifica la sua lettura per un po '. – kirilloid

+0

Se si utilizza 'RegexOptions.ExplicitCapture', sarà un po 'più veloce e non sarà necessario utilizzare quei gruppi' (?:) '. –

risposta

3

L'espressione sembra buono nel complesso, come altri hanno detto, potrebbe essere un po 'prolisso con tutte le {0,1} invece di ? e (?: invece di applicare RegexOptions.ExplicitCapture. Ma questi non dovrebbero rendere l'espressione lenta. Risulteranno solo in una migliore leggibilità.

Ciò che potrebbe causare lentezza è il fatto che ci sono molte opzioni di backtracking nell'espressione facendo sia il mese espanso che il.opzionale. Mi chiedo cosa succederebbe se modificassi l'espressione per applicare solo l'opzionale. una volta, dopo il nome del mese, e che cosa accadrebbe se hai fatto il nome del mese un gruppo avido

In modo che ((?>pattern) Nonbacktracking (o "avidi") subexpression.):

(jan(?:uary){0,1}\.{0,1}|feb(?:ruary){0,1}\.{0,1}|mar(?:ch){0,1}\.{0,1}|apr(?:il){0,1}\.{0,1}|may\.{0,1}|jun(?:e){0,1}\.{0,1}|jul(?:y){0,1}\.{0,1}|aug(?:ust){0,1}\.{0,1}|sep(?:tember){0,1}\.{0,1}|oct(?:ober){0,1}\.{0,1}|nov(?:ember){0,1}\.{0,1}|dec(?:ember){0,1}\.{0,1})\s+(\d{2,4})) 

diventerebbero:

(?>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|june?|july?|aug(ust)?|sep(tember)?|oct(ober)?|nov(ember)?|dec(ember)?)\.?\s+(\d{2,4})) 

Non solo è molto più breve, mi aspetterei che fosse più veloce.

E poi c'è la parte dell'espressione all'inizio, che per me non ha senso (?:(\d{1,4})- /.- /.) O qualcosa si è perso nella formattazione, o questo non aiuta un po '.

\ d {1,4} avrebbe senso per un anno o qualsiasi altra parte di data, ma il - /.- /. dopo non ha alcun senso. Penso che intendessi qualcosa del tipo:

\d{1,4}[- /.]\d{1,2}[- /.]\d{1,2} 

O qualcosa in quella zona. Così com'è cattura immondizia, probabilmente non accelerando il processo di abbinamento.

Alla fine sono d'accordo con Aliostad, che probabilmente stai meglio cercando di trovare un modello meno preciso per trovare i candidati iniziali, quindi restringere i risultati utilizzando DateTime.TryParseExact o con un set aggiuntivo di espressioni.

Invece di creare un'espressione 'globale' per trovare i candidati, è possibile utilizzare un sacco di espressioni esatte. Vedrai che con Regex è spesso più economico eseguire un numero di espressioni esatte su un input di grandi dimensioni, piuttosto che eseguire un'espressione con un sacco di 's e?' S in là.

Così rompere la tua ricerca in molteplici espressioni molto precise potrebbe comportare una performance molto più alto, questi potrebbero essere un inizio:

\b\d{1,2}[- .\\/]\d{1,2}[- .\\/](\d{2}|\d{4})\b 
\b((jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)(.|[a-z]{0,10})|\d{1,2})[- .\\/,]\d{1,2}[- .\\/,](\d{2}|\d{4})\b 

Come si può vedere tutti i gruppi opzionali sono stati rimossi da queste espressioni, rendendo loro molto più veloce da eseguire. Ho anche rimosso l'esatta ortografia dai nomi dei mesi, poiché probabilmente vorrai accettare "sept" e "sep" nonché "settembre"

Rompere il pattern migliora anche la leggibilità :).

Un ultimo consiglio: limita la quantità di possibili caratteri di cui hai bisogno per tornare indietro, ponendo un limite su cose come \ s +, raramente vuoi abbinare 20.000 spazi, ma se sono nel tuo documento sorgente, proveranno abbinarli. \ s {1,20} è in genere sufficiente e limita la capacità del motore di cercare di ottenere una corrispondenza dove non ce n'è davvero una.

+0

Questo è stato un commento davvero utile, grazie. – meirlo

+0

+1 Buona analisi. – Aliostad

3

E 'difficile trovare un algoritmo senza prove. Potremmo raccomandare qualcosa che si rivela più lento. Quindi davvero sta provando diverse opzioni.

L'espressione sembra un po 'prolissa ma non posso dire che sia la causa del problema. 2 secondi per un file di grandi dimensioni è OK, ma non per un file più piccolo quindi è tutto in relazione alla dimensione del lavoro che sta facendo


Un approccio posso consigliare sta avendo un processo in due fasi.

Il primo è lo screening per pescare quelli che corrispondono più probabilmente e l'altro è quello di esaminare ulteriormente solo quella sezione del file in cui si trova la corrispondenza. Ad esempio, '\ d {1,2} \ s *, \ s * \ d {4}' è probabile che faccia parte di una data, ma cercarla è meglio che cercare tutte le condizioni riguardanti Jan (uary)/Feb (braio)/mar (ch)/....


e un piccolo consiglio: prima ottenere le metriche destra, fare i compiti di stabilire le metriche di base prima di iniziare qualsiasi cambiamento.

Se si desidera migliorare le prestazioni, è necessario disporre di alcune metriche difficili e veloci prima ancora di tentare di migliorare.

Problemi correlati