Suggerimenti per Regex Golf


43

Simile ai nostri thread per suggerimenti sul golf specifici della lingua: quali sono i trucchi generali per abbreviare le espressioni regolari?

Vedo tre usi di regex quando si tratta di golf: il classico regex golf ("ecco un elenco che dovrebbe corrispondere, ed ecco un elenco che dovrebbe fallire"), usando regex per risolvere problemi computazionali ed espressioni regolari usate come parti di codice da golf più grande. Sentiti libero di postare suggerimenti su uno o tutti questi. Se il tuo suggerimento è limitato a uno o più gusti, indica questi sapori in alto.

Come al solito, si prega di attenersi a un suggerimento (o famiglia di suggerimenti strettamente correlati) per risposta, in modo che i suggerimenti più utili possano salire ai vertici tramite votazione.


Flagrante autopromozione: in quale categoria di regex-use rientra? codegolf.stackexchange.com/a/37685/8048
Kyle Strand

@KyleStrand "espressioni regolari utilizzate come parti di un codice più grande."
Martin Ender

Risposte:


24

Quando non fuggire

Queste regole si applicano alla maggior parte dei gusti, se non a tutti:

  • ] non ha bisogno di scappare quando non ha eguali.

  • {e }non hanno bisogno di scappare quando non fanno parte di una ripetizione, ad esempio le {a}partite {a}letteralmente. Anche se vuoi abbinare qualcosa del genere {2}, devi solo scappare da uno di essi, ad es {2\}.

Nelle classi di caratteri:

  • ]non ha bisogno di fuggire quando è il primo carattere di un set di caratteri, ad esempio, []abc]corrisponde a uno dei ]abc, o quando è il secondo personaggio, dopo una ^, ad esempio, [^]]corrisponde a qualsiasi cosa ma ]. (Notevole eccezione: sapore ECMAScript!)

  • [non ha bisogno di scappare affatto. Insieme al suggerimento sopra, questo significa che puoi abbinare entrambe le parentesi con la classe di personaggi orribilmente contro-intuitiva [][].

  • ^non ha bisogno di scappare quando non è il primo personaggio di un set di caratteri, ad es [ab^c].

  • -non ha bisogno di scappare quando è il primo (secondo dopo a ^) o l'ultimo carattere in un set di caratteri, ad esempio [-abc], [^-abc]oppure [abc-].

  • Nessun altro personaggio ha bisogno di fuggire all'interno di una classe di caratteri, anche se sono meta-personaggi al di fuori delle classi di caratteri (tranne la barra rovesciata \).

Inoltre, in alcuni gusti ^e $vengono abbinati letteralmente quando non sono rispettivamente all'inizio o alla fine della regex.

(Grazie a @ MartinBüttner per aver compilato alcuni dettagli)


Alcuni preferiscono sfuggire al punto effettivo racchiudendolo in una classe di caratteri in cui non è necessario sfuggire (ad esempio [.]). Scapparlo normalmente salverebbe 1 byte in questo caso\.
CSᵠ

Nota che [deve essere evaso in Java. Non sono sicuro di ICU (utilizzato in Android e iOS) o .NET, però.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳

18

Una semplice espressione regolare per abbinare tutti i caratteri stampabili nella tabella ASCII .

[ -~]

1
pura bellezza, tutti i caratteri di una tastiera americana standard! nota: la tabella ascii standard (esclusa la gamma estesa 127-255
CSᵠ

Lo uso spesso, ma manca un personaggio "normale" comune: TAB. E si presume che tu stia usando LC_ALL = "C" (o simile) in quanto alcune altre impostazioni locali falliranno.
Olivier Dulac,

Il trattino può essere usato in questo modo per specificare un intervallo di caratteri nella tabella ASCII? Funziona con tutti i gusti di regex?
Josh Withee,

14

Conosci i tuoi sapori regex

C'è una quantità sorprendente di persone che pensano che le espressioni regolari siano essenzialmente agnostiche del linguaggio. Tuttavia, in realtà ci sono differenze abbastanza sostanziali tra i sapori, e soprattutto per il golf del codice è bene conoscerne alcuni e le loro caratteristiche interessanti, in modo da poter scegliere il meglio per ogni attività. Ecco una panoramica di diversi sapori importanti e ciò che li distingue dagli altri. (Questo elenco non può davvero essere completo, ma fammi sapere se mi sono perso qualcosa di veramente evidente.)

Perl e PCRE

Li sto lanciando in un singolo piatto, poiché non ho troppa familiarità con il sapore Perl e sono per lo più equivalenti (dopotutto PCRE è per le espressioni regolari compatibili con Perl). Il vantaggio principale del sapore Perl è che puoi effettivamente chiamare il codice Perl dall'interno della regex e della sostituzione.

  • Ricorsione / subroutine . Probabilmente la caratteristica più importante per il golf (che esiste solo in un paio di gusti).
  • Modelli condizionali (?(group)yes|no).
  • Sostiene il cambiamento del caso nella stringa di sostituzione con \l, \u, \Le \U.
  • PCRE consente l'alternanza in lookbehinds, dove ogni alternativa può avere una lunghezza diversa (ma fissa). (La maggior parte dei gusti, incluso il Perl, richiede che i lookbehind abbiano una lunghezza fissa complessiva.)
  • \G per ancorare una partita alla fine della partita precedente.
  • \K per resettare l'inizio della partita
  • PCRE supporta sia le proprietà dei caratteri Unicode che gli script .
  • \Q...\Eper sfuggire a serie più lunghe di personaggi. Utile quando stai cercando di abbinare una stringa che contiene molti meta-caratteri.

.NETTO

Questo è probabilmente il sapore più potente, con solo poche carenze.

Una lacuna importante in termini di golf è che non supporta quantificatori possessivi come altri sapori. Invece di .?+dover scrivere (?>.?).

Giava

  • A causa di un bug (vedi Appendice) Java supporta un tipo limitato di lookbehind di lunghezza variabile: puoi guardare indietro fino all'inizio della stringa con .*da dove puoi ora avviare un lookahead, come (?<=(?=lookahead).*).
  • Supporta l'unione e l'intersezione delle classi di caratteri.
  • Ha il supporto più esteso per Unicode, con classi di caratteri per "Script Unicode, blocchi, categorie e proprietà binarie" .
  • \Q...\E come in Perl / PCRE.

Rubino

Nelle versioni recenti, questo sapore è altrettanto potente di PCRE, incluso il supporto per le chiamate di subroutine. Come Java, supporta anche l'unione e l'intersezione delle classi di caratteri. Una caratteristica speciale è la classe di caratteri integrata per le cifre esadecimali: \h(e negata \H).

La caratteristica più utile per giocare a golf è come Ruby gestisce i quantificatori. In particolare, è possibile nidificare i quantificatori senza parentesi. .{5,7}+funziona e così fa .{3}?. Inoltre, a differenza della maggior parte degli altri sapori, se il limite inferiore di un quantificatore è 0che può essere omesso, ad esempio .{,5}è equivalente a .{0,5}.

Per quanto riguarda le subroutine, la principale differenza tra le subroutine PCRE e le subroutine di Ruby è che la sintassi di Ruby è un byte più lunga (?n)rispetto a \g<n>, ma le subroutine di Ruby possono essere utilizzate per l'acquisizione, mentre PCRE ripristina le acquisizioni al termine di una subroutine.

Infine, Ruby ha una semantica diversa per i modificatori relativi alla linea rispetto alla maggior parte degli altri sapori. Il modificatore che di solito viene chiamato min altri gusti è sempre attivo in Ruby. Quindi ^e $abbina sempre l'inizio e la fine di una riga, non solo l'inizio e la fine della stringa. Questo può farti risparmiare un byte se hai bisogno di questo comportamento, ma ti costerà byte extra se non lo fai, perché dovrai sostituire ^e $con \Ae \z, rispettivamente. In aggiunta a ciò, il modificatore che di solito viene chiamato s(che fa .corrispondere gli avanzamenti di riga) viene invece chiamato min Ruby. Ciò non influisce sul conteggio dei byte, ma deve essere tenuto presente per evitare confusione.

Pitone

Python ha un sapore solido, ma non sono a conoscenza di funzioni particolarmente utili che non troverai da nessun'altra parte.

Tuttavia , esiste un sapore alternativo che è destinato a sostituire il remodulo ad un certo punto e che contiene molte caratteristiche interessanti. Oltre ad aggiungere il supporto per la ricorsione, i lookbehinds di lunghezza variabile e gli operatori di combinazione di classi di caratteri, ha anche la caratteristica unica della corrispondenza fuzzy . In sostanza è possibile specificare un numero di errori (inserimenti, eliminazioni, sostituzioni) consentiti e il motore fornirà anche corrispondenze approssimative.

ECMAScript

Il sapore di ECMAScript è molto limitato e quindi raramente molto utile per giocare a golf. L'unica cosa che sta succedendo è che la classe di caratteri vuota negata si [^] abbina a qualsiasi personaggio così come la classe di caratteri vuota che fallisce incondizionatamente [](al contrario del solito (?!)). Sfortunatamente, il sapore non ha alcuna caratteristica che lo rende utile per i normali problemi.

Lua

Lua ha il suo sapore abbastanza unico, che è piuttosto limitato (ad esempio non puoi nemmeno quantificare i gruppi) ma presenta una manciata di funzioni utili e interessanti.

  • Ha un gran numero di abbreviazioni per le classi di caratteri integrate , tra cui punteggiatura, caratteri maiuscoli / minuscoli e cifre esadecimali.
  • Con %besso supporta una sintassi molto compatta per abbinare stringhe bilanciate. Ad esempio %b()abbina a (e quindi tutto fino a un abbinamento )(saltando correttamente coppie accoppiate interne). (e )può contenere due caratteri qui.

Incremento

Il sapore regex di Boost è essenzialmente quello di Perl. Tuttavia, ha alcune nuove e interessanti funzionalità per la sostituzione di regex, tra cui cambiamenti di caso e condizioni . Quest'ultimo è unico per Boost per quanto ne so.


Si noti che il look-ahead nel look-behind punterà attraverso il limite associato nel look-behind. Testato in Java e PCRE.
n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳

Non è .?+equivalente a .*?
CalculatorFeline

@CalculatorFeline Il primo è un quantificatore possessivo 0-o-1 (in sapori che supportano quantificatori possessivi), il secondo è un quantificatore 0-o-più.
Martin Ender,

@CalculatorFeline ah Capisco la confusione. C'era un errore di battitura.
Martin Ender,

13

Conosci le tue classi di personaggi

La maggior parte dei sapori regex hanno classi di caratteri predefinite. Ad esempio, \dcorrisponde a una cifra decimale, che è di tre byte più corta di [0-9]. Sì, potrebbero essere leggermente diversi in quanto \dpotrebbero corrispondere anche alle cifre Unicode in alcuni gusti, ma per la maggior parte delle sfide questo non farà differenza.

Ecco alcune classi di caratteri che si trovano nella maggior parte dei sapori regex:

\d      Match a decimal digit character
\s      Match a whitespace character
\w      Match a word character (typically [a-zA-Z0-9_])

Inoltre, abbiamo anche:

\D \S \W

quali sono le versioni negate di quanto sopra.

Assicurati di controllare il tuo sapore per eventuali classi di caratteri aggiuntive che potrebbe avere. Ad esempio, PCRE ha \Rper le nuove righe e Lua ha persino classi come caratteri minuscoli e maiuscoli.

(Grazie a @HamZa e @ MartinBüttner per avermelo fatto notare)


3
\Rper newline in PCRE.
HamZa

12

Non preoccuparti di gruppi non acquisiti (a meno che ...)

Questo suggerimento si applica a (almeno) tutti i popolari sapori ispirati al Perl.

Questo può essere ovvio, ma (quando non si gioca a golf) è buona norma usare gruppi non catturanti (?:...)quando possibile. Questi due personaggi extra ?:sono dispendiosi quando si gioca a golf, quindi basta usare gruppi di cattura, anche se non li rimanderai indietro.

C'è una (rara) eccezione: se ti capita di tornare 10al gruppo di backreference almeno 3 volte, puoi effettivamente salvare i byte trasformando un gruppo precedente in un gruppo non di acquisizione, in modo che tutti quelli \10diventino \9. (Trucchi simili si applicano, se si utilizza il gruppo 11almeno 5 volte e così via.)


Perché 11 ha bisogno di 5 volte per valerne la pena quando 10 ne richiede 3?
Nic Hartley,

1
@QPaysTax è in grado di utilizzare $9invece di $10o $11salva una volta un byte. La trasformazione $10in $9richiede uno ?:, che è di due byte, quindi avrai bisogno di tre $10secondi per salvare qualcosa. La trasformazione $11in $9due richiede quattro ?:s, che sono quattro byte, quindi avrai bisogno di cinque $11s per salvare qualcosa (o cinque $10e $11combinati).
Martin Ender,

10

Ricorsione per riutilizzo del modello

Una manciata di sapori supporta la ricorsione ( per quanto ne so, Perl, PCRE e Ruby). Anche quando non stai cercando di risolvere problemi ricorsivi, questa funzione può salvare molti byte in schemi più complicati. Non è necessario effettuare la chiamata a un altro gruppo (denominato o numerato) all'interno del gruppo stesso. Se hai un certo schema che appare più volte nella tua regex, raggruppalo e fai riferimento ad esso al di fuori di quel gruppo. Questo non è diverso da una chiamata di subroutine nei normali linguaggi di programmazione. Quindi invece di

...someComplexPatternHere...someComplexPatternHere...someComplexPatternHere... 

in Perl / PCRE potresti fare:

...(someComplexPatternHere)...(?1)...(?1)...

o in Ruby:

...(someComplexPatternHere)...\g<1>...\g<1>...

purché sia ​​il primo gruppo (ovviamente, è possibile utilizzare qualsiasi numero nella chiamata ricorsiva).

Si noti che questo non è lo stesso di un backreference ( \1). I riferimenti indietro corrispondono esattamente alla stessa stringa con cui il gruppo ha eseguito l'ultima volta. Queste chiamate di subroutine in realtà valutano nuovamente il modello. Come esempio per someComplexPatternHereprendere una lunga classe di caratteri:

a[0_B!$]b[0_B!$]c[0_B!$]d

Questo corrisponderebbe a qualcosa del genere

aBb0c!d

Si noti che non è possibile utilizzare i riferimenti indietro qui preservando il comportamento. Un backreference fallirebbe nella stringa sopra, perché Be 0e !non sono gli stessi. Tuttavia, con le chiamate di subroutine, il modello viene effettivamente rivalutato. Il modello sopra è completamente equivalente a

a([0_B!$])b(?1)c(?1)d

Catturare in chiamate di subroutine

Una nota di cautela per Perl e PCRE: se il gruppo 1negli esempi sopra contiene ulteriori gruppi, le chiamate della subroutine non ricorderanno le loro acquisizioni. Considera questo esempio:

(\w(\d):)\2 (?1)\2 (?1)\2

Questo non corrisponderà

x1:1 y2:2 z3:3

perché dopo il ritorno delle chiamate della subroutine, la nuova acquisizione del gruppo 2viene scartata. Invece, questo modello corrisponderebbe a questa stringa:

x1:1 y2:1 z3:1

Questo è diverso da rubino, dove le chiamate subroutine fanno conservare le loro cattura, quindi l'equivalente rubino regex (\w(\d):)\2 \g<1>\2 \g<1>\2dovrebbe corrispondere al primo degli esempi precedenti.


Puoi usare \1per Javascript. E anche PHP (immagino).
Ismael Miguel,

5
@IsmaelMiguel Questo non è un backreference. Questo in realtà valuta di nuovo il modello. Ad esempio (..)\1corrisponderebbe ababma fallirebbe abbamentre (..)(?1)corrisponderà a quest'ultimo. In realtà è una chiamata di subroutine nel senso che l'espressione viene applicata di nuovo, invece di corrispondere letteralmente a ciò che corrisponde l'ultima volta.
Martin Ender,

Wow, non ne avevo idea! Imparare qualcosa di nuovo ogni giorno
Ismael Miguel,

In .NET (o altri gusti senza questa funzione):(?=a.b.c)(.[0_B!$]){3}d
jimmy23013

@ user23013 che sembra molto specifico per questo esempio particolare. Non sono sicuro che sia applicabile se riutilizzo un certo sottotatro in vari lookaround.
Martin Ender,

9

Provocare una partita per fallire

Quando si utilizza regex per risolvere problemi computazionali o abbinare linguaggi altamente non regolari, a volte è necessario far fallire un ramo del modello indipendentemente da dove ci si trovi nella stringa. L'approccio ingenuo è usare un lookahead negativo vuoto:

(?!)

Il contenuto (il modello vuoto) corrisponde sempre, quindi il lookahead negativo fallisce sempre. Ma il più delle volte, c'è un'opzione molto più semplice: basta usare un personaggio che sai che non comparirà mai nell'input. Ad esempio, se sai che il tuo input consisterà sempre solo di cifre, puoi semplicemente usare

!

o qualsiasi altro carattere non numerico e non meta per causare errori.

Anche se il tuo input potrebbe potenzialmente contenere sottostringhe, ci sono modi più brevi di (?!). Qualsiasi sapore che permetta alle ancore di apparire all'interno di uno schema rispetto all'estremità, potrebbe usare una delle seguenti soluzioni a 2 caratteri:

a^
$a

Si noti tuttavia che alcuni sapori tratteranno ^e $come personaggi letterali in queste posizioni, perché ovviamente non hanno senso come ancore.

Nel sapore ECMAScript c'è anche l'elegante soluzione a 2 caratteri

[]

Questa è una classe di caratteri vuota, che cerca di assicurarsi che i personaggi successivi siano uno di quelli della classe - ma non ci sono personaggi nella classe, quindi questo fallisce sempre. Nota che questo non funzionerà in nessun altro modo, perché le classi di personaggi non possono essere vuote.


8

Ottimizza i tuoi OR

Ogni volta che hai 3 o più alternative nel tuo RegEx:

/aliceblue|antiquewhite|aquamarine|azure/

Controlla se c'è un inizio comune:

/a(liceblue|ntiquewhite|quamarine|zure)/

E forse anche un finale comune?

/a(liceblu|ntiquewhit|quamarin|zur)e/

Nota: 3 è solo l'inizio e rappresenterebbe la stessa lunghezza, 4+ farebbe la differenza


Ma cosa succede se non tutti hanno un prefisso comune? (spazi bianchi aggiunti solo per chiarezza)

/aliceblue|antiquewhite|aqua|aquamarine|azure
|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood
|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan/

Raggruppali, a condizione che la regola 3+ abbia senso:

/a(liceblue|ntiquewhite|qua|quamarine|zure)
|b(eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood)
|c(adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

O anche generalizzare se l'entropia soddisfa il tuo caso d'uso:

/\w(liceblue|ntiquewhite|qua|quamarine|zure
|eige|isque|lack|lanchedalmond|lue|lueviolet|rown|urlywood
|adetblue|hartreuse|hocolate|oral|ornflowerblue|ornsilk|rimson|yan)/

^ in questo caso siamo sicuri di non averne nessuna clueocrown slack Ryan

Questo "secondo alcuni test" migliora anche le prestazioni, in quanto fornisce un punto di ancoraggio da cui iniziare.


1
Se l'inizio o la fine comuni sono più lunghi di un carattere, anche il raggruppamento di due può fare la differenza. Come aqua|aquamarineaqua(|marine)o aqua(marine)?.
Paŭlo Ebermann,

6

Questo è abbastanza semplice, ma vale la pena affermare:

Se vi trovate a ripetere la classe di caratteri [a-zA-Z]probabilmente si può semplicemente utilizzare [a-z]e aggiungere il i(caso- i nsensitive modificatore) al vostro regex.

Ad esempio, in Ruby, le seguenti due regex sono equivalenti:

/[a-zA-Z]+\d{3}[a-zA-Z]+/
/[a-z]+\d{3}[a-z]/i - 7 byte più brevi

Del resto, anche gli altri modificatori possono ridurre la lunghezza totale. Invece di fare questo:

/(.|\n)/

che corrisponde a qualsiasi carattere (a causa di punti non corrisponde a capo), utilizzare le s ingle-line modificatore s, il che rende punti a capo partita.

/./s - 3 byte più brevi


In Ruby, ci sono un sacco di classi di personaggi integrate per regex. Vedi questa pagina e cerca "Proprietà personaggio".
Un ottimo esempio è il "simbolo della valuta". Secondo Wikipedia ci sono un sacco di possibili simboli di valuta e metterli in una classe di caratteri sarebbe molto costoso ( [$฿¢₡Ð₫€.....]) mentre puoi abbinarli a 6 byte:\p{Sc}


1
Tranne JavaScript, in cui il smodificatore non è supportato. :( Ma lì puoi usare il /[^]/trucco proprietario di JavaScript .
Manatwork

Nota che (.|\n)non funziona nemmeno in alcuni gusti, perché .spesso non corrisponde ad altri tipi di separatori di linea. Tuttavia, il modo consueto per farlo (senza s) è [\s\S]quale è lo stesso byte di (.|\n).
Martin Ender,

@ MartinBüttner, la mia idea era di tenerlo insieme agli altri suggerimenti relativi alla fine della linea. Ma se ritieni che questa risposta riguardi più i modificatori, non ho obiezioni se la ripubbidi.
arte

@manatwork done (e ha aggiunto anche un trucco specifico non ES)
Martin Ender

6

Un semplice parser di lingua

È possibile creare un parser molto semplice con un RE simile \d+|\w+|".*?"|\n|\S . I token che devi abbinare sono separati dal carattere RE 'o'.

Ogni volta che il motore RE tenta di abbinare la posizione corrente nel testo, proverà il primo modello, quindi il secondo, ecc. Se fallisce (ad esempio su un carattere spazio), si sposta e riprova le corrispondenze . L'ordine è importante. Se abbiamo inserito il \Stermine prima del \d+termine, il\S corrisponderebbe per primo a qualsiasi carattere non spaziale che rompa il nostro parser.

Il ".*?"matcher di stringhe usa un modificatore non avido, quindi abbiniamo solo una stringa alla volta. Se il tuo RE non ha funzioni non avide, puoi usare ciò "[^"]*"che è equivalente.

Esempio Python:

text = 'd="dogfinder"\nx=sum(ord(c)*872 for c in "fish"+d[3:])'
pat = r'\d+|\w+|".*?"|\n|\S'
print re.findall(pat, text)

['d', '=', '"dogfinder"', '\n', 'x', '=', 'sum', '(', 'ord', '(', 'c', ')',
    '*', '872', 'for', 'c', 'in', '"fish"', '+', 'd', '[', '3', ':', ']', ')']

Esempio Golfed Python:

# assume we have language text in A, and a token processing function P
map(P,findall(r'\d+|\w+|".*?"|\n|\S',A))

È possibile regolare i motivi e il loro ordine per la lingua che è necessario abbinare. Questa tecnica funziona bene per JSON, HTML di base ed espressioni numeriche. È stato usato con successo molte volte con Python 2, ma dovrebbe essere abbastanza generale da funzionare in altri ambienti.


6

\K invece di un lookbehind positivo

PCRE e Perl supportano la sequenza di escape \K, che ripristina l'inizio della partita. Ciò ab\Kcdrichiederà che la stringa di input contenga, abcdma sarà solo la corrispondenza segnalata cd.

Se stai usando un lookbehind positivo all'inizio del tuo pattern (che è probabilmente il posto più probabile), nella maggior parte dei casi puoi \Kinvece usare e salvare 3 byte:

(?<=abc)def
abc\Kdef

Questo è equivalente per la maggior parte degli scopi, ma non del tutto. Le differenze comportano vantaggi e svantaggi:

  • Upside: PCRE e Perl non supportano lookbehind di lunghezza arbitraria (solo .NET lo fa). Cioè, non puoi fare qualcosa del genere (?<=ab*). Ma con \Kte puoi mettere qualsiasi tipo di motivo davanti ad esso! Quindi ab*\Kfunziona. Questo in realtà rende questa tecnica molto più potente nei casi in cui è applicabile.
  • Upside: i lookaround non fanno marcia indietro. Ciò è rilevante se si desidera catturare qualcosa nel lookbehind per tornare indietro in un secondo momento, ma ci sono diverse possibili acquisizioni che portano tutte a corrispondenze valide. In questo caso, il motore regex proverebbe solo una di queste possibilità. Quando si utilizza \Kquella parte del regex, viene eseguito il backtracking come tutto il resto.
  • Unico inconveniente: come probabilmente saprai, diverse partite di una regex non possono sovrapporsi. Spesso, le soluzioni alternative vengono utilizzate per aggirare parzialmente questa limitazione, poiché il lookahead può convalidare una parte della stringa che era già stata consumata da una corrispondenza precedente. Quindi, se volevi abbinare tutti i personaggi che seguivi ab , potresti usare (?<=ab).. Dato l'input

    ababc
    

    questo corrisponderebbe al secondo ae al c. Questo non può essere riprodotto con \K. Se lo avessi usato ab\K., otterrai solo la prima partita, perché ora abnon è in uno sguardo.


Se un modello utilizza la \Ksequenza di escape all'interno di un'asserzione positiva, l'inizio riportato di una partita corretta può essere maggiore della fine della partita.
hwnd

@hwnd Il mio punto è che dato ababc, non c'è modo di abbinare sia il secondo ache il ccon \K. Riceverai solo una partita.
Martin Ender,

Hai ragione, non con la funzione stessa. Dovresti ancorare con\G
hwnd

@hwnd Ah vedo il tuo punto ora. Ma immagino che a quel punto (dal punto di vista del golf) stai meglio con uno sguardo negativo, perché in realtà potresti addirittura averne bisogno in quanto non puoi essere sicuro che .dall'ultima partita sia stato effettivamente un a.
Martin Ender,

1
Uso interessante di \ K =)
hwnd

5

Abbinando qualsiasi personaggio

Al sapore di ECMAScript mancano i smodificatori che fanno .corrispondere qualsiasi personaggio (comprese le nuove linee). Ciò significa che non esiste una soluzione a carattere singolo per abbinare caratteri completamente arbitrari. La soluzione standard in altri gusti (quando non si desidera utilizzare sper qualche motivo) è [\s\S]. Tuttavia, ECMAScript è l'unico sapore (a mia conoscenza) che supporta classi di personaggi vuoti, e quindi ha un molto più breve alternativa: [^]. Questa è una classe di caratteri vuota negata, ovvero corrisponde a qualsiasi personaggio.

Anche per altri gusti, possiamo imparare da questa tecnica: se non vogliamo usare s(ad esempio perché abbiamo ancora bisogno del significato solito .in altri luoghi), può esserci ancora un modo più breve per abbinare sia i caratteri di nuova riga che quelli stampabili, purché ci sia un personaggio che sappiamo non appare nell'input. Supponiamo che stiamo elaborando numeri delimitati da newline. Quindi possiamo abbinare qualsiasi personaggio con [^!], poiché sappiamo che !non farà mai parte della stringa. Ciò consente di risparmiare due byte sull'ingenuo [\s\S]o [\d\n].


4
In Perl, \Nsignifica esattamente cosa .significa al di fuori della /smodalità, tranne che non è influenzato da una modalità.
Konrad Borowski il

4

Usa gruppi atomici e quantificatori possessivi

Ho trovato gruppi atomici ( (?>...)) e quantificatori possessivi ( ?+, *+, ++,{m,n}+ ) a volte molto utili per giocare a golf. Corrisponde a una stringa e non consente il backtracking successivo. Quindi corrisponderà solo alla prima stringa accoppiabile trovata dal motore regex.

Ad esempio: per far corrispondere una stringa con un numero dispari di aall'inizio, che non è seguito da altri a, puoi usare:

^(aa)*+a
^(?>(aa)*)a

Questo ti permette di usare cose come .* liberamente, e se c'è una corrispondenza ovvia, non ci sarà un'altra possibilità che corrisponda a troppi o troppo pochi caratteri, il che potrebbe interrompere il tuo schema.

In .NET regex (che non ha quantificatori possessivi), puoi usarlo per far apparire il gruppo 1 il più grande multiplo di 3 (con un massimo di 30) volte (non giocato molto bene):

(?>((?<-1>){3}|){10})

1
Nell'ECMAscript mancano anche quantificatori possessivi o gruppi atomici :(
CSᵠ

4

Dimentica un gruppo acquisito dopo una sottoespressione (PCRE)

Per questa regex:

^((a)(?=\2))(?!\2)

Se si desidera cancellare \ 2 dopo il gruppo 1, è possibile utilizzare la ricorsione:

^((a)(?=\2)){0}(?1)(?!\2)

Corrisponderà aamentre il precedente no. A volte puoi anche usare ??o anche ?al posto di {0}.

Questo potrebbe essere utile se hai usato molte ricorsioni e alcuni dei riferimenti o gruppi condizionali sono apparsi in diversi punti della tua regex.

Si noti inoltre che i gruppi atomici sono assunti per le ricorsioni in PCRE. Quindi questo non corrisponderà a una singola lettera a:

^(a?){0}(?1)a

Non l'ho ancora provato in altri gusti.

Per i lookahead, puoi anche usare doppi negativi per questo scopo:

^(?!(?!(a)(?=\1))).(?!\1)

4

Espressioni opzionali

A volte è utile ricordarlo

(abc)?

è per lo più lo stesso di

(abc|)

C'è una piccola differenza però: nel primo caso, il gruppo cattura abco non cattura affatto. Quest'ultimo caso farebbe fallire incondizionatamente un backreference. Nella seconda espressione, il gruppo acquisirà abco una stringa vuota, in cui quest'ultimo caso farebbe corrispondere incondizionatamente un backreference . Per emulare quest'ultimo comportamento, ?dovresti circondare tutto in un altro gruppo che costerebbe due byte:

((abc)?)

La versione che utilizza |è utile anche quando vuoi avvolgere l'espressione in qualche altra forma di gruppo e non ti interessa l'acquisizione:

(?=(abc)?)
(?=abc|)

(?>(abc)?)
(?>abc|)

Infine, questo trucco può essere applicato anche a ungreedy in ?cui salva un byte anche nella sua forma grezza (e di conseguenza 3 byte se combinato con altre forme di gruppi):

(abc)??
(|abc)

1

Lookahead multipli che corrispondono sempre (.NET)

Se hai 3 o più costrutti lookahead che corrispondono sempre (per catturare le sottoespressioni), o c'è un quantificatore su un lookahead seguito da qualcos'altro, quindi dovrebbero essere in un gruppo non necessariamente catturato:

(?=a)(?=b)(?=c)
((?=a)b){...}

Questi sono più brevi:

(?(?(?(a)b)c))
(?(a)b){...}

dove anon dovrebbe essere il nome di un gruppo acquisito. Non puoi usare |per dire la solita cosa dentro be csenza aggiungere un'altra coppia di parentesi.

Sfortunatamente, i gruppi di bilanciamento nei condizionali sembravano buggy, rendendolo inutile in molti casi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.