Percursos
/
Factor
Factor
/
Exercícios
/
Vigia do Livro-Razão
Vigia do Livro-Razão

Vigia do Livro-Razão

Exercício de aprendizagem

Introdução

Já conheces o básico: classes de carateres como \d e [A-Z], quantificadores como {4} e +, e as palavras matches?, all-matching-subseqs e re-replace. Este exercício acrescenta as partes das expressões regulares que descrevem estrutura e contexto. Encontram-se no mesmo vocabulário regexp e escrevem-se com o mesmo literal R/ /.

Âncoras

As âncoras correspondem a uma posição e não a um caráter:

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

Como $ significa o fim da string, escreve \$ quando queres dizer um sinal de dólar literal.

Um limite de palavra (\b) é o ponto onde um caráter de palavra encontra algo que não é um caráter de palavra. Os carateres de palavra são letras, algarismos e o underscore; tudo o resto (espaços, pontuação e as duas extremidades da string) conta como limite. Por isso, \bcat\b só corresponde a cat quando este aparece isolado:

USING: regexp ;

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

Agrupamento e alternância

Os parênteses curvos ( ) agrupam parte de um padrão, para que um quantificador (ou um |, que significa "isto ou aquilo") se aplique ao grupo inteiro:

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)

O último padrão lê-se assim: "um sinal de dólar, um ou mais algarismos e, depois, um grupo opcional formado por um ponto e dois algarismos".

Nota: em Factor, um grupo apenas estrutura o padrão. Ao contrário de algumas ferramentas de regex, não consegues recuperar o texto com que um grupo correspondeu: não há extração de grupos de captura, e referências anteriores como \1 não são suportadas. Para extrair texto em torno de um ponto de referência, usa antes o lookaround (abaixo).

Lookaround: corresponde por contexto, sem o consumir

Um lookaround verifica o que vem imediatamente antes ou imediatamente depois do ponto atual, sem tornar esses carateres parte da correspondência. É de "largura zero": o que testa não é incluído no resultado.

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

O lookaround é seletivo: os mesmos carateres correspondem ou não, consoante o que está ao lado deles. Um simples \d+ apanha todos os números; o lookbehind e o lookahead ficam apenas com os que têm o vizinho certo:

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)

Opções

Acrescenta uma letra depois do / de fecho para alterar o comportamento do padrão inteiro. O i faz com que não distinga maiúsculas de minúsculas:

USING: regexp ;

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

Transformar cada correspondência

O all-matching-subseqs entrega-te o texto correspondido. O map-matches vai um passo mais longe: executa uma quotation em cada correspondência e recolhe os resultados. A quotation recebe o índice start da correspondência, o índice end e a string completa; o subseq transforma esses três elementos no texto correspondido, que depois 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)

O seu primo que só produz efeitos secundários, o each-match, executa uma quotation em cada correspondência sem recolher nada.

Instruções

Tens a contabilidade de uma pequena empresa e, todos os dias, chegam transações em linhas de texto desorganizadas. Vais usar expressões regulares para validar montantes, extrair valores pelo símbolo que os marca e sinalizar linhas que precisam de uma análise mais atenta.

1. Validar um montante

Um montante bem formado é um $, um ou mais algarismos e uma parte de cêntimos opcional, composta por um ponto seguido de exatamente dois algarismos: $100 ou $100.50, mas não $100.5. Define valid-amount?, que devolve t quando a string inteira é um montante bem formado.

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

2. Extrair os valores em dólares

Define dollar-amounts, que devolve todos os números que aparecem imediatamente a seguir a um $ na linha (apenas os algarismos, sem o $), por ordem.

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

3. Extrair as percentagens

Define percentages, que devolve todos os números que são imediatamente seguidos por um %.

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

4. Sinalizar uma linha

Define flagged?, que devolve t quando a linha menciona refund ou chargeback em qualquer combinação de maiúsculas e minúsculas.

"Issued a REFUND today" flagged? .   ! => t
"a normal sale"         flagged? .   ! => f
Editar via GitHub A ligação abre numa nova janela ou separador
Factor Exercism

Estás pronto para começar Vigia do Livro-Razão?

Inscreve-te no Exercism para aprenderes e dominares Factor com 47 conceitos163 exercícios, e mentoria humana real, tudo grátis.