Rutas
/
Factor
Factor
/
Ejercicios
/
Vigía del libro mayor
Vigía del libro mayor

Vigía del libro mayor

Ejercicio de aprendizaje

Introducción

Ya conoces lo básico: las clases de caracteres como \d y [A-Z], los cuantificadores como {4} y +, y las palabras matches?, all-matching-subseqs y re-replace. Este ejercicio añade las partes de las expresiones regulares que describen estructura y contexto. Viven en el mismo vocabulario regexp, escritas con el mismo literal R/ /.

Anclas

Las anclas coinciden con una posición en lugar de con un carácter:

^    ! the start of the string
$    ! the end of the string
\b   ! a word boundary

Como $ significa el final del string, escribe \$ cuando quieras decir un signo de dólar literal.

Un límite de palabra (\b) es el punto en el que un carácter de palabra se encuentra con uno que no lo es. Los caracteres de palabra son las letras, los dígitos y el guion bajo; todo lo demás (los espacios, la puntuación y los dos extremos del string) cuenta como límite. Así, \bcat\b coincide con cat solo cuando aparece por sí solo:

USING: regexp ;

"cat nap" R/ \bcat\b/ re-contains? .   ! => t
"scatter" R/ \bcat\b/ re-contains? .   ! => f   (cat sits inside a word)

Agrupación y alternancia

Los paréntesis ( ) agrupan parte de un patrón para que un cuantificador (o un |, «esto o aquello») se aplique a todo el grupo:

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)

El último patrón se lee «un signo de dólar, uno o más dígitos y, después, un grupo opcional formado por un punto y dos dígitos».

Nota: en Factor, un grupo solo estructura el patrón. A diferencia de otras herramientas de expresiones regulares, no puedes recuperar el texto con el que ha coincidido un grupo: no hay extracción de grupos de captura y las retrorreferencias como \1 no son compatibles. Para extraer texto alrededor de una referencia, usa en su lugar la lookaround (más abajo).

Lookaround: coincide por contexto, sin consumirlo

Una lookaround comprueba lo que hay justo antes o justo después del punto actual sin que esos caracteres formen parte de la coincidencia. Tiene «ancho cero»: lo que comprueba no se incluye en el resultado.

(?=...)    ! lookahead:           ... must follow
(?!...)    ! negative lookahead:  ... must NOT follow
(?<=...)   ! lookbehind:          ... must come before
(?<!...)   ! negative lookbehind: ... must NOT come before

La lookaround es selectiva: los mismos caracteres coinciden o no en función de lo que tengan al lado. Un simple \d+ captura todos los números; el lookbehind y el lookahead conservan solo los que tienen el vecino adecuado:

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)

Opciones

Añade una letra después de la / de cierre para cambiar el comportamiento de todo el patrón. i hace que no distinga entre mayúsculas y minúsculas:

USING: regexp ;

"REFUND"  R/ refund/i matches? .       ! => t
"Refund"  R/ refund/i matches? .       ! => t

Transformar cada coincidencia

all-matching-subseqs te da el texto coincidente. map-matches va un paso más allá: ejecuta una quotation en cada coincidencia y recoge los resultados. La quotation recibe el índice start de la coincidencia, su índice end y el string completo; subseq convierte esos tres elementos en el texto coincidente, que después transformas:

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)

Su prima each-match, que solo produce efectos secundarios, ejecuta una quotation en cada coincidencia sin recoger nada.

Instrucciones

Llevas las cuentas de una pequeña empresa y cada día las transacciones llegan como líneas de texto desordenadas. Usarás expresiones regulares para validar importes, extraer cifras a partir del símbolo que las marca y marcar las líneas que necesitan un repaso más detenido.

1. Valida un importe

Un importe bien formado consta de un $, uno o más dígitos y una parte de céntimos opcional formada por un punto seguido de exactamente dos dígitos: $100 o $100.50, pero no $100.5. Define valid-amount?, que devuelve t cuando el string entero es un importe bien formado.

"$100.50" valid-amount? .   ! => t
"$100"    valid-amount? .   ! => t
"$100.5"  valid-amount? .   ! => f

2. Extrae las cifras en dólares

Define dollar-amounts, que devuelve todos los números que siguen directamente a un $ en la línea (solo los dígitos, sin el $), en orden.

"spent $100 and $25 today" dollar-amounts .
! => { "100" "25" }

3. Extrae los porcentajes

Define percentages, que devuelve todos los números a los que sigue inmediatamente un %.

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

4. Marca una línea

Define flagged?, que devuelve t cuando la línea menciona refund o chargeback con cualquier combinación de mayúsculas y minúsculas.

"Issued a REFUND today" flagged? .   ! => t
"a normal sale"         flagged? .   ! => f
Editar en GitHub El enlace se abre en una ventana o pestaña nueva
Factor Exercism

¿Listo para empezar Vigía del libro mayor?

Regístrate en Exercism para aprender y dominar Factor con 47 conceptos163 ejercicios y mentoría humana real, todo gratis.