Catturare quantificatori e quantificare l’aritmetica

All’inizio, vorrei spiegare che questa domanda non riguarda né come catturare i gruppi, né come usare i quantificatori, due caratteristiche di regex con cui ho perfettamente familiarità. È più una domanda avanzata per gli amanti delle regex che potrebbero avere familiarità con una syntax insolita nei motori esotici.

Catturare i quantificatori

Qualcuno sa se un sapore regex ti permette di acquisire quantificatori? Con questo, intendo che il numero di caratteri corrispondenti ai quantificatori come + e * verrebbe contato e che questo numero potrebbe essere riutilizzato in un altro quantificatore.

Ad esempio, supponiamo di voler essere sicuro di avere lo stesso numero di Ls e R in questo tipo di stringa: LLLRRRRR

Potresti immaginare una syntax come

L(+)R{\q1} 

dove viene catturato il quantificatore + per la L e dove viene indicato il numero catturato nel quantificatore per la R come {\ q1}

Questo sarebbe utile per bilanciare il numero di {@, =, -, /} in stringhe come @@@@ “Star Wars” ==== “1977” —- “Fantascienza” //// ” George Lucas ”

Relazione con la ricorsione

In alcuni casi la cattura del quantificatore sostituisce elegantemente la ricorsione, ad esempio una parte di testo incorniciata dallo stesso numero di Ls e Rs, una in

 L(+) some_content R{\q1} 

L’idea è presentata in alcuni dettagli nella pagina seguente: Quantificatori catturati

Descrive anche un’estensione naturale ai quantificatori catturati: l’aritmetica quantificata, per le occasioni in cui si desidera abbinare (3 * x + 1) il numero di caratteri identificati in precedenza.

Sto cercando di scoprire se qualcosa di simile esiste.

Grazie in anticipo per i tuoi approfondimenti !!!

Aggiornare

Casimir ha dato una risposta fantastica che mostra due metodi per convalidare che le varie parti di un modello hanno la stessa lunghezza. Tuttavia, non vorrei fare affidamento su nessuno di questi per il lavoro quotidiano. Questi sono davvero trucchi che dimostrano grande spettacolarità. Nella mia mente, questi metodi belli ma complessi confermano la premessa della domanda: una funzione regex per catturare il numero di caratteri che quanti quanti (come + o *) sono in grado di eguagliare renderebbe tali schemi di bilanciamento molto semplici ed estendere la syntax in un modo piacevolmente espressivo.

Aggiornamento 2 (molto più tardi)

Ho scoperto che .NET ha una caratteristica che si avvicina a quello che stavo chiedendo. Aggiunta una risposta per dimostrare la funzionalità.

Non conosco un motore regex in grado di acquisire un quantificatore. Tuttavia, con PCRE o Perl è ansible utilizzare alcuni trucchi per verificare se si dispone dello stesso numero di caratteri. Con il tuo esempio:

  @@@@ "Star Wars" ==== "1977" ---- "Fantascienza" //// "George Lucas" 

puoi controllare se @ = - / sono bilanciati con questo modello che usa il famoso trucco Qtax , (sei pronto?): il “gruppo autoreferenziale facoltativo-facoltativo”

 ~(? 

dettagli del modello:

 ~ # pattern delimiter (? 

L'idea principale

Il gruppo non catturante contiene solo un @ . Ogni volta che questo gruppo viene ripetuto, viene aggiunto un nuovo carattere nei gruppi di cattura 2, 3 e 4.

il gruppo autoreferenziale facoltativo-facoltativo

Come funziona?

 ( (?: @ (?= [^=]* (\2?+ = ) .....) )+ ) 

Alla prima occorrenza del carattere @ il gruppo di cattura 2 non è ancora definito, quindi non è ansible scrivere qualcosa del genere (\2 =) che faccia fallire il modello. Per evitare il problema, il modo è rendere facoltativo il backreference: \2?

Il secondo aspetto di questo gruppo è che il numero di caratteri = identificati viene incrementato ad ogni ripetizione del gruppo non catturante, poiché an = viene aggiunto ogni volta. Per garantire che questo numero aumenti sempre (o il modello fallisca), il quantificatore possessivo forza il backreference a corrispondere prima di aggiungere un nuovo carattere = .

Nota che questo gruppo può essere visto in questo modo: se esiste il gruppo 2, allora abbinalo con il prossimo =

 ( (?(2)\2) = ) 

Il modo ricorsivo

 ~(?[^@=]+|(?-1))*=)(?!=))(?=(@(?>[^@-]+|(?-1))*-)(?!-))(?=(@(?>[^@/]+|(?-1))*/)(?!/))~ 

È necessario utilizzare corrispondenze sovrapposte, poiché si utilizzerà la parte @ più volte, è la ragione per cui tutti i pattern sono all'interno di lookaround.

dettagli del modello:

 (? # open an atomic group [^@=]+ # all that is not an @ or an =, one or more times | # OR (?-1) # recursion: the last defined capturing group (the current here) )* # repeat zero or more the atomic group = # ) # close the capture group (?!=) # checks the = boundary ) # close the lookahead (?=(@(?>[^@-]+|(?-1))*-)(?!-)) # the same for - (?=(@(?>[^@/]+|(?-1))*/)(?!/)) # the same for / 

La principale differenza con il modello precedente è che a questo non interessa l'ordine di = - e / gruppi. (Tuttavia è ansible apportare facilmente alcune modifiche al primo pattern per affrontarlo, con classi di caratteri e lookaheads negativi).

Nota: per la stringa di esempio, per essere più specifici, è ansible sostituire il lookbehind negativo con un ancoraggio ( ^ o \A ). E se vuoi ottenere l'intera stringa come risultato della partita devi aggiungere .* Alla fine (altrimenti il ​​risultato della partita sarà vuoto man mano che il giocoso lo noterà).

Tornando indietro di cinque settimane dopo ho appreso che .NET ha qualcosa che si avvicina molto all’idea di “acquisizione del quantificatore” menzionata nella domanda. La funzione è chiamata “gruppi di bilanciamento”.

Ecco la soluzione che ho trovato. Sembra lungo, ma è piuttosto semplice.

 (?:@(?)(?)(?))+[^@=]+(?<-c1>=)+[^=-]+(?<-c2>-)+[^-/]+(?<-c3>/)+[^/]+(?(c1)(?!))(?(c2)(?!))(?(c3)(?!)) 

Come funziona?

  1. Il primo gruppo non catturante corrisponde ai caratteri @ . In quel gruppo non catturante, abbiamo tre gruppi con nome c1, c2 e c3 che non corrispondono a nulla, o piuttosto, che corrispondono a una stringa vuota. Questi gruppi fungeranno da tre contatori c1, c2 e c3. Poiché .NET tiene traccia delle acquisizioni intermedie quando un gruppo viene quantificato, ogni volta che viene abbinato un @ , viene aggiunta un’acquisizione alle raccolte di cattura per i gruppi c1, c2 e c3.

  2. Successivamente, [^@=]+ mangia tutti i caratteri fino al primo = .

  3. Il secondo gruppo quantificato (?<-c1>=)+ corrisponde ai caratteri = . Quel gruppo sembra essere chiamato -c1 , ma -c1 non è un nome di gruppo. -c1 is.NET syntax per eseguire il pop di una cattura dalla collezione di cattura del gruppo c1 nell’etere. In altre parole, ci consente di decrementare c1. Se provi a decrementare c1 quando la collezione di cattura è vuota, la partita fallisce. Ciò garantisce che non possiamo mai avere più = di @ caratteri. (Più tardi, dovremo assicurarci che non possiamo avere più @ than = caratteri.)

  4. I passi successivi ripetono i punti 2 e 3 per i caratteri - e / , decrementando i contatori c2 e c3.

  5. [^/]+ Mangia il resto della stringa.

  6. Il (?(c1)(?!)) è un condizionale che dice “Se il gruppo c1 è stato impostato, allora fallisce”. Potresti sapere che (?!) È un trucco comune per forzare una regex a fallire. Questo condizionale assicura che c1 sia stato decrementato fino a zero: in altre parole, non ci possono essere più @ che = caratteri.

  7. Allo stesso modo, il (?(c2)(?!)) e (?(c3)(?!)) assicurano che non ci possano essere più @ che - e / caratteri.

Non so voi, ma anche questo è un po ‘lungo, lo trovo davvero intuitivo.