Parcours
/
Factor
Factor
/
Exercices
/
La sentinelle du registre
La sentinelle du registre

La sentinelle du registre

Exercice d'apprentissage

Introduction

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

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 groupes et l'alternance

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 \1 ne sont pas prises en charge. Pour extraire du texte autour d'un repère, utilise plutôt les lookaround (ci-dessous).

Les lookaround : correspondre par le contexte sans le consommer

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)

Les options

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

Transformer chaque correspondance

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.

Instructions

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.

1. Valide un montant

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

2. Extrais les montants en dollars

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" }

3. Extrais les pourcentages

Définis percentages, qui renvoie chaque nombre immédiatement suivi d'un %.

"up 5% then down 12%" percentages .
! => { "5" "12" }

4. Signale une ligne

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
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Factor Exercism

Prêt à commencer La sentinelle du registre ?

Inscris-toi sur Exercism pour apprendre et maîtriser Factor avec 47 concepts163 exercices, et un vrai mentorat humain, le tout gratuitement.