Trilhas
/
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

Você já conhece o básico: classes de caracteres 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. Elas vivem no mesmo vocabulário regexp, escritas com o mesmo literal R/ /.

Âncoras

As âncoras correspondem a uma posição, e não a um caractere:

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

Como $ significa o fim da string, escreva \$ quando quiser dizer um cifrão literal.

Uma fronteira de palavra (\b) é o ponto onde um caractere de palavra encontra um que não é de palavra. Os caracteres de palavra são letras, dígitos e o sublinhado; todo o resto (espaços, pontuação e as duas pontas da string) conta como fronteira. Então \bcat\b corresponde a cat apenas quando ele aparece sozinho:

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 redondos ( ) agrupam parte de um padrão para que um quantificador, ou um | ("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 se lê assim: "um cifrão, um ou mais dígitos e, em seguida, um grupo opcional formado por um ponto e dois dígitos".

Nota: no Factor, um grupo apenas estrutura o padrão. Diferente de algumas ferramentas de regex, você não consegue recuperar o texto com que um grupo correspondeu: não há extração de grupos de captura, e referências retroativas como \1 não são suportadas. Para extrair texto em volta de um ponto de referência, use o lookaround (veja abaixo).

Lookaround: correspondência pelo contexto, sem consumir o contexto

Um lookaround verifica o que vem logo antes ou logo depois do ponto atual sem tornar esses caracteres parte da correspondência. Ele tem "largura zero": o que ele testa não entra 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 caracteres correspondem ou não, dependendo do que está ao lado deles. Um \d+ simples pega todos os números; o lookbehind e o lookahead mantêm apenas 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

Adicione uma letra depois da / de fechamento para mudar o comportamento do padrão inteiro. O i o torna insensível a maiúsculas e minúsculas:

USING: regexp ;

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

Transformar cada correspondência

all-matching-subseqs entrega o texto da correspondência. O map-matches vai um passo além: ele roda uma quotation em cada correspondência e junta os resultados. A quotation recebe o índice start da correspondência, o índice end dela e a string inteira; o subseq transforma esses três no texto da correspondência, que você então transforma:

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)

Seu primo que só produz efeitos colaterais, each-match, roda uma quotation em cada correspondência sem juntar nada.

Instruções

Você cuida da contabilidade de uma pequena empresa, e as transações de cada dia chegam como linhas de texto desorganizadas. Você vai usar expressões regulares para validar valores, extrair números pelo símbolo que os marca e sinalizar as linhas que precisam de uma olhada mais de perto.

1. Valide um valor

Um valor bem formado é um $, um ou mais dígitos e uma parte de centavos opcional, formada por um ponto seguido de exatamente dois dígitos: $100 ou $100.50, mas não $100.5. Defina valid-amount?, que retorna t quando a string inteira for um valor bem formado.

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

2. Extraia os valores em dólar

Defina dollar-amounts, que retorna todo número que vem logo depois de um $ na linha (apenas os dígitos, sem o $), na ordem.

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

3. Extraia as porcentagens

Defina percentages, que retorna todo número seguido imediatamente por um %.

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

4. Sinalize uma linha

Defina flagged?, que retorna 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 O link abre em uma nova janela ou aba
Factor Exercism

Tudo pronto para começar Vigia do livro-razão?

Crie sua conta no Exercism para aprender e dominar Factor com 47 conceitos163 exercícios e mentoria humana de verdade, tudo de graça.