Percursos
/
jq
jq
/
Exercícios
/
Chatbot de Expressões Regulares
Chatbot de Expressões Regulares

Chatbot de Expressões Regulares

Exercício de aprendizagem

Introdução

Expressões regulares

As expressões regulares (regex) são sequências de carateres que especificam um padrão de pesquisa num texto.

Aprender a sintaxe das expressões regulares está fora do âmbito deste tópico. Vamos centrar-nos nas expressões que o jq disponibiliza para trabalhar com regex.

Variante de regex

Ferramentas diferentes implementam versões diferentes de expressões regulares. O jq incorpora a biblioteca de expressões regulares Oniguruma, que é em grande parte compatível com as expressões regulares do Perl v5.8.

A sintaxe específica usada pelo jq pode ser encontrada no repositório GitHub da Oniguruma.

Caution

O jq não tem nenhuma sintaxe especial para expressões regulares. São simplesmente expressas como strings. Isso significa que todas as barras invertidas na expressão regular têm de ser escapadas na string.

Por exemplo, a classe de carateres de algarismos (\d) tem de ser escrita como "\\d".

Funções de regex

As expressões regulares no jq estão limitadas a um conjunto de filtros.

Correspondência simples

Quando precisas de saber se uma string corresponde a um padrão, usa o filtro test.

STRING | test(REGEX)
STRING | test(REGEX; FLAGS)
STRING | test([REGEX, FLAGS])

Este filtro devolve um resultado boolean.

"Hello World!" | test("W")    # => true
"Goodbye Mars" | test("W")    # => false
Informação sobre a correspondência

Quando precisas de extrair a substring que correspondeu ao padrão, usa o filtro match.

STRING | match(REGEX)
STRING | match(REGEX; FLAGS)
STRING | match([REGEX, FLAGS])

Este filtro devolve:

  • nada, se não houver correspondência, ou
  • um objeto com várias propriedades, se houver correspondência.

Este exemplo procura duas vogais consecutivas idênticas, usando a sintaxe de retroreferência, \1.

"Hello World!" | match("([aeiou])\\1")
# => empty

"Goodbye Mars" | match("([aeiou])\\1")
# => {
#      "offset": 1,
#      "length": 2,
#      "string": "oo",
#      "captures": [
#        {
#          "offset": 1,
#          "length": 1,
#          "string": "o",
#          "name": null
#        }
#      ]
#    }

O filtro match devolve um objeto por cada correspondência. Este exemplo mostra o flag "g" em ação, para encontrar todas as vogais.

"Goodbye Mars" | match("[aeiou]"; "g")
# => { "offset": 1, "length": 1, "string": "o", "captures": [] }
#    { "offset": 2, "length": 1, "string": "o", "captures": [] }
#    { "offset": 6, "length": 1, "string": "e", "captures": [] }
#    { "offset": 9, "length": 1, "string": "a", "captures": [] }
Substrings capturadas

Tal como o filtro match, o filtro capture devolve um objeto se houver uma correspondência.

STRING | capture(REGEX)
STRING | capture(REGEX; FLAGS)
STRING | capture([REGEX, FLAGS])

O objeto devolvido é um mapeamento das capturas nomeadas.

"JIRAISSUE-1234" | capture("(?<project>\\w+)-(?<issue_num>\\d+)")
# => {
#      "project": "JIRAISSUE",
#      "issue_num": "1234"
#    }
Apenas as substrings

O filtro scan é semelhante ao match com o flag "g".

STRING | scan(REGEX)
STRING | scan(REGEX; FLAGS)

O scan devolve um stream de substrings.

"Goodbye Mars" | scan("[aeiou]")
# => "o"
#    "o"
#    "e"
#    "a"

Usa o construtor de arrays [...] para capturar as substrings.

"Goodbye Mars" | [ scan("[aeiou]") ]
# => ["o", "o", "e", "a"]
Dividir uma string

Se conheces as partes da string que queres manter, usa o match ou o scan. Se conheces as partes que queres descartar, usa o split.

STRING | split(REGEX; FLAGS)
Caution

O filtro split de aridade 1 trata o seu argumento como uma string fixa.

Para usar uma regex com o split, tens de fornecer o 2.º argumento; não há problema em usar uma string vazia.

Um exemplo que divide uma string por espaços em branco arbitrários.

"first   second           third fourth" | split("\\s+"; "")
# => ["first", "second", "third", "fourth"]
Substituições

Os filtros sub e gsub podem transformar a string de entrada, substituindo as partes correspondentes por uma string de substituição.

Para substituir apenas a primeira correspondência, usa o sub. Para substituir todas as correspondências, usa o gsub.

STRING | sub(REGEX; REPLACEMENT)
STRING | sub(REGEX; REPLACEMENT; FLAGS)
STRING | gsub(REGEX; REPLACEMENT)
STRING | gsub(REGEX; REPLACEMENT; FLAGS)
"Goodnight kittens. Goodnight mittens." | sub("night"; " morning")
# => "Good morning kittens. Goodnight mittens."

"Goodnight kittens. Goodnight mittens." | gsub("night"; " morning")
# => "Good morning kittens. Good morning mittens."

O texto de substituição pode referir-se às substrings correspondentes; usa capturas nomeadas e interpolação de strings.

"Some 3-letter acronyms: gnu, csv, png"
| gsub( "\\b(?<tla>[[:alpha:]]{3})\\b";     # find words 3 letters long
        "\(.tla | ascii_upcase)" )          # upper-case the match
# => "Some 3-letter acronyms: GNU, CSV, PNG"

Flags

Em todos os filtros acima, FLAGS é uma string composta por zero ou mais dos flags suportados.

  • g - Pesquisa global (encontra todas as correspondências, não apenas a primeira)
  • i - Pesquisa que não distingue maiúsculas de minúsculas
  • m - Modo multilinha (o '.' corresponde a mudanças de linha)
  • n - Ignora correspondências vazias
  • p - Os modos s e m estão ambos ativados
  • s - Modo de linha única ('^' -> '\A', '$' -> '\Z')
  • l - Encontra as correspondências mais longas possíveis
  • x - Formato de regex extendido (ignora espaços em branco e comentários)

Por exemplo

"JIRAISSUE-1234" | capture("(?<project>\\w+)-(?<issue_num>\\d+)")

# or with Extended formatting

"JIRAISSUE-1234" | capture("
                     (?<project>   \\w+ )  # the Jira project
                     -                     # followed by a hyphen
                     (?<issue_num> \\d+ )  # followed by digits
                   "; "x")

Instruções

Foste contratado como Especialista em Expressões Regulares numa empresa que está a desenvolver um Chatbot.

Está numa fase de desenvolvimento muito básica. A tua missão é usar expressões regulares para melhorar a capacidade do Chatbot de compreender e gerar linguagem natural.

1. Verifica se o comando é válido

Além de ser inteligente, o Chatbot também é um assistente leal. Para pedir algo ao Chatbot, o utilizador tem de dizer a palavra "Chatbot" na primeira posição do comando. Não importa se a palavra-chave está em MAIÚSCULAS ou minúsculas. O importante é a posição da palavra.

Implementa a função is_valid_command que ajuda o Chatbot a reconhecer quando o utilizador está a dar um comando.

"Chatbot, play a song from the 80's." | is_valid_command
# => true
"Hey Chatbot, where is the closest pharmacy?" | is_valid_command
# => false
"CHATBOT, do you have a solution for this challenge?" | is_valid_command
# => true

2. Remove os emojis encriptados

O Chatbot tem dificuldade em compreender como as pessoas usam emojis para expressar as suas emoções. Quando o Chatbot recebe mensagens dos utilizadores, cada emoji é representado pela string "emoji" seguida de um número de id.

Implementa o método remove_emoji, que recebe uma string e remove todos os emojis da mensagem.

As linhas que não contêm emojis devem ser devolvidas sem alterações. Remove apenas a string do emoji. Não ajustes os espaços em branco.

"I love playing videogames emoji3465 it's one of my hobbies" | remove_emoji
# => "I love playing videogames  it's one of my hobbies"

3. Verifica se o número de telefone é válido

Em algum momento da interação com o Chatbot, o utilizador vai fornecer um número de telefone. O Chatbot só consegue ligar para um número com um formato específico.

Implementa a função check_phone_number.

Se o número for válido, o Chatbot responde com uma mensagem a agradecer ao utilizador e a confirmar o número. Se o número for inválido, o Chatbot informa o utilizador de que o número de telefone não é válido.

O formato esperado é (+NN) NNN-NNN-NNN, em que N é um algarismo.

"chatbot my phone number is (+34) 659-771-594" | check_phone_number
# => "Thanks! Your phone number is OK."
"chatbot, call me at 659-771-594" | check_phone_number
# => "Oops, it seems like I can't reach out to 659-771-594."

O Chatbot é um software muito curioso. Apesar de conseguir procurar na internet um tema específico, gosta de perguntar aos utilizadores por sites interessantes para visitar e encontrar informação relevante.

Exemplo de conversa:

Chatbot: Olá, Utilizador, gostava de aprender a programar em JavaScript. Conheces algum site interessante onde possa aprender?

Utilizador: Aprendi muito no exercism.org, há lá coisas excelentes.

Implementa a função get_domains que devolve um array de domínios de sites.

"I learned a lot from exercism.org and google.com" | get_domains
# => ["exercism.org", "google.com"]

5. Cumprimenta o utilizador

Um Chatbot educado fala com os utilizadores pelo nome. Quando um utilizador se apresenta, o nosso Chatbot deteta o nome e responde com uma saudação amigável.

Escreve a função nice_to_meet_you.

Se a string de entrada contiver "My name is Someone.", captura o nome e devolve a string "Nice to meet you, Someone.".

"My name is Jean-Luc" | nice_to_meet_you
# => "Nice to meet you, Jean-Luc"

6. Análise de CSV muito simples

Cedendo à «febre das funcionalidades», vamos adicionar ao Chatbot uma função de análise de CSV.

Implementa a função parse_csv que recebe uma string e devolve um array dos campos resultantes. O separador de campos deve ser «vírgula mais espaços em branco opcionais».

Não nos vamos preocupar com nenhum dos casos limite do formato CSV (como campos que contêm vírgulas).

"first, second,third,   fourth" | parse_csv
# => ["first", "second", "third", "fourth"]
Editar via GitHub A ligação abre numa nova janela ou separador
jq Exercism

Estás pronto para começar Chatbot de Expressões Regulares?

Inscreve-te no Exercism para aprenderes e dominares jq com 12 conceitos75 exercícios, e mentoria humana real, tudo grátis.