Conosci già le basi: classi di caratteri come \d e [A-Z],
quantificatori come {4} e +, e le parole matches?,
all-matching-subseqs e re-replace. Questo esercizio aggiungerà le parti
delle espressioni regolari che descrivono struttura e contesto. Vivono
nello stesso vocabolario regexp, scritto con lo stesso
letterale R/ /.
Le ancore corrispondono a una posizione invece che a un carattere:
^ ! the start of the string
$ ! the end of the string
\b ! a word boundary
Dato che $ indica la fine della stringa, scrivi \$ quando vuoi
indicare un simbolo del dollaro letterale.
Un confine di parola (\b) è il punto in cui un carattere di parola
incontra un carattere che non lo è. I caratteri di parola sono lettere,
cifre e il trattino basso; tutto il resto (spazi, punteggiatura e le due
estremità della stringa) conta come confine. Quindi \bcat\b corrisponde
a cat solo quando è da solo:
USING: regexp ;
"cat nap" R/ \bcat\b/ re-contains? . ! => t
"scatter" R/ \bcat\b/ re-contains? . ! => f (cat sits inside a word)
Le parentesi tonde ( ) raggruppano parte di un pattern, così che un
quantificatore, o una | (che significa «questo o quello»), si
applichi all'intero gruppo:
USING: regexp ;
"abab" R/ (ab)+/ matches? . ! => t (+ applies to "ab")
"dog" R/ cat|dog/ matches? . ! => t (either word)
"$100.50" R/ \$\d+(\.\d{2})?/ matches? . ! => t
"$100" R/ \$\d+(\.\d{2})?/ matches? . ! => t (the group is optional)
L'ultimo pattern si legge così: «un simbolo del dollaro, una o più cifre, poi un gruppo opzionale formato da un punto e due cifre».
Nota: in Factor un gruppo si limita a strutturare il pattern. A differenza di alcuni strumenti per le espressioni regolari, non puoi recuperare il testo con cui il gruppo ha corrisposto: non esiste l'estrazione dei gruppi di cattura, e i backreference come
\1non sono supportati. Per estrarre il testo attorno a un punto di riferimento, usa invece il lookaround (più avanti).
Un lookaround controlla cosa viene subito prima o subito dopo il punto corrente senza rendere quei caratteri parte della corrispondenza. È a «larghezza zero»: ciò che verifica non è incluso nel risultato.
(?=...) ! lookahead: ... must follow
(?!...) ! negative lookahead: ... must NOT follow
(?<=...) ! lookbehind: ... must come before
(?<!...) ! negative lookbehind: ... must NOT come before
Il lookaround è selettivo: gli stessi caratteri corrispondono o no a
seconda di cosa hanno accanto. Un semplice \d+ prende tutti i numeri; il
lookbehind e il lookahead tengono solo quelli con il vicino giusto:
USING: regexp ;
"3 for $10 or 10 for $30" R/ \d+/ all-matching-subseqs .
! => { "3" "10" "10" "30" } (every number)
"3 for $10 or 10 for $30" R/ (?<=\$)\d+/ all-matching-subseqs .
! => { "10" "30" } (only the prices — the bare 3 and 10 drop)
"buy 2 at 15% off, 4 at 30% off" R/ \d+(?=%)/ all-matching-subseqs .
! => { "15" "30" } (only the discounts — the bare 2 and 4 drop)
Aggiungi una lettera dopo la / di chiusura per cambiare il comportamento
dell'intero pattern. La i lo rende insensibile alle maiuscole e alle
minuscole:
USING: regexp ;
"REFUND" R/ refund/i matches? . ! => t
"Refund" R/ refund/i matches? . ! => t
all-matching-subseqs ti dà il testo corrispondente. map-matches fa un
passo in più: esegue una quotation su ogni corrispondenza e raccoglie i
risultati. La quotation riceve l'indice start della corrispondenza, il
suo indice end e l'intera stringa; subseq trasforma quei tre elementi
nel testo corrispondente, che poi puoi trasformare:
map-matches ( string regexp quot: ( start end string -- obj ) -- seq )
USING: math regexp sequences ;
"a1 b22 c333" R/ \d+/ [ subseq length ] map-matches .
! => { 1 2 3 } (the length of each run of digits)
Il suo cugino che si limita agli effetti collaterali,
each-match, esegue una quotation su ogni corrispondenza
senza raccogliere nulla.
Tieni la contabilità di una piccola azienda, ed ogni giorno le transazioni arrivano sotto forma di righe di testo disordinate. Userai le espressioni regolari per convalidare gli importi, estrarre le cifre in base al simbolo che le contrassegna e segnalare le righe che meritano un'occhiata più attenta.
Un importo ben formato è composto da un $, una o più cifre e una parte in centesimi facoltativa formata da un punto seguito da esattamente due cifre: $100 o $100.50, ma non $100.5. Definisci valid-amount?, che restituisce t quando l'intera stringa è un importo ben formato.
"$100.50" valid-amount? . ! => t
"$100" valid-amount? . ! => t
"$100.5" valid-amount? . ! => f
Definisci dollar-amounts, che restituisce, in ordine, ogni numero che segue direttamente un $ nella riga: le sole cifre, senza il $.
"spent $100 and $25 today" dollar-amounts .
! => { "100" "25" }
Definisci percentages, che restituisce ogni numero seguito immediatamente da un %.
"up 5% then down 12%" percentages .
! => { "5" "12" }
Definisci flagged?, che restituisce t quando la riga menziona refund o chargeback, in qualsiasi combinazione di maiuscole e minuscole.
"Issued a REFUND today" flagged? . ! => t
"a normal sale" flagged? . ! => f
Iscriviti a Exercism per imparare e padroneggiare Factor con 47 concetti163 esercizi e il mentoring di persone reali, tutto gratis.