Tu connais déjà les bases : les classes de caractères comme \d et [A-Z], les quantificateurs comme {4} et +, et les mots matches?, all-matching-subseqs et re-replace. Cet exercice ajoute les parties des expressions régulières qui décrivent la structure et le contexte. Elles se trouvent dans le même vocabulaire regexp, écrites avec le même littéral R/ /.
Les ancres correspondent à une position plutôt qu'à un caractère :
^ ! the start of the string
$ ! the end of the string
\b ! a word boundary
Comme $ désigne la fin de la string, écris \$ quand tu veux dire un signe dollar littéral.
Une limite de mot (\b) est l'endroit où un caractère de mot rencontre un caractère qui n'en est pas un. Les caractères de mot sont les lettres, les chiffres et le tiret bas ; tout le reste, les espaces, la ponctuation et les deux extrémités de la string, compte comme une limite. Ainsi, \bcat\b ne correspond à cat que lorsque celui-ci se trouve isolé :
USING: regexp ;
"cat nap" R/ \bcat\b/ re-contains? . ! => t
"scatter" R/ \bcat\b/ re-contains? . ! => f (cat sits inside a word)
Les parenthèses ( ) regroupent une partie d'un motif pour qu'un quantificateur, ou un | (« ceci ou cela »), s'applique à tout le groupe :
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)
Le dernier motif se lit « un signe dollar, un ou plusieurs chiffres, puis un groupe facultatif composé d'un point et de deux chiffres ».
Remarque : dans Factor, un groupe ne fait que structurer le motif. Contrairement à certains outils d'expressions régulières, tu ne peux pas récupérer le texte auquel un groupe a correspondu : il n'y a pas d'extraction de groupe de capture, et les rétroréférences comme
\1ne sont pas prises en charge. Pour extraire du texte autour d'un repère, utilise plutôt les lookaround (ci-dessous).
Un lookaround vérifie ce qui précède ou ce qui suit immédiatement l'endroit courant sans inclure ces caractères dans la correspondance. Il est « de largeur nulle » : ce qu'il teste n'est pas inclus dans le résultat.
(?=...) ! lookahead: ... must follow
(?!...) ! negative lookahead: ... must NOT follow
(?<=...) ! lookbehind: ... must come before
(?<!...) ! negative lookbehind: ... must NOT come before
Les lookaround sont sélectifs : les mêmes caractères correspondent ou non selon ce qui se trouve à côté d'eux. Un simple \d+ attrape tous les nombres ; le lookbehind et le lookahead ne gardent que ceux qui ont le bon voisin :
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)
Ajoute une lettre après le / de fermeture pour modifier le comportement de tout le motif. i le rend insensible à la casse :
USING: regexp ;
"REFUND" R/ refund/i matches? . ! => t
"Refund" R/ refund/i matches? . ! => t
all-matching-subseqs te donne le texte correspondant. map-matches va plus loin : il exécute une quotation sur chaque correspondance et rassemble les résultats. La quotation reçoit l'indice start de la correspondance, son indice end et la string entière ; subseq transforme ces trois éléments en texte correspondant, que tu transformes ensuite :
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)
Son cousin qui ne produit que des effets de bord, each-match, exécute une quotation sur chaque correspondance sans rien rassembler.
Tu tiens les comptes d'une petite entreprise, et les transactions de chaque jour arrivent sous forme de lignes de texte en désordre. Tu vas utiliser des expressions régulières pour valider des montants, extraire les chiffres grâce au symbole qui les marque, et signaler les lignes qui méritent un examen plus attentif.
Un montant bien formé est un $, un ou plusieurs chiffres, et une partie en centimes facultative composée d'un point suivi d'exactement deux chiffres : $100 ou $100.50, mais pas $100.5. Définis valid-amount?, qui renvoie t quand la chaîne entière est un montant bien formé.
"$100.50" valid-amount? . ! => t
"$100" valid-amount? . ! => t
"$100.5" valid-amount? . ! => f
Définis dollar-amounts, qui renvoie chaque nombre qui suit directement un $ dans la ligne (les chiffres uniquement, sans le $), dans l'ordre.
"spent $100 and $25 today" dollar-amounts .
! => { "100" "25" }
Définis percentages, qui renvoie chaque nombre immédiatement suivi d'un %.
"up 5% then down 12%" percentages .
! => { "5" "12" }
Définis flagged?, qui renvoie t quand la ligne mentionne refund ou chargeback, quelle que soit la combinaison de majuscules et de minuscules.
"Issued a REFUND today" flagged? . ! => t
"a normal sale" flagged? . ! => f
Inscris-toi sur Exercism pour apprendre et maîtriser Factor avec 47 concepts163 exercices, et un vrai mentorat humain, le tout gratuitement.