Percursos
/
Elixir
Elixir
/
Exercícios
/
Analisador de datas
Analisador de datas

Analisador de datas

Exercício de aprendizagem

Introdução

Expressões regulares

As expressões regulares (regex) são uma ferramenta poderosa para trabalhar com strings em Elixir. As expressões regulares em Elixir seguem a especificação PCRE (Perl Compatible Regular Expressions). Os padrões de string que representam o significado da expressão regular são primeiro compilados e só depois usados para corresponder a toda a string ou a parte dela.

Em Elixir, a forma mais comum de criar expressões regulares é usar o sigil ~r. Os sigils fornecem atalhos de açúcar sintático para tarefas comuns em Elixir. Para corresponder a um literal de string, podemos usar a própria string como padrão a seguir ao sigil.

~r/test/

O operador =~/2 é útil para efetuar uma correspondência de regex numa string e devolver um resultado boolean.

"this is a test" =~ ~r/test/
# => true

Duas notas sobre a utilização de sigils:

  • podes usar muitos delimitadores diferentes, em vez de /, consoante aquilo de que precisas
  • os padrões de string já estão escapados; se escreveres o padrão como string, sem usar uma regex, tens de escapar as barras invertidas (\)

Classes de carateres

Corresponder a um intervalo de carateres com parênteses retos [] define uma classe de carateres. Isto corresponde a um único caráter pertencente à classe. Também podes especificar um intervalo de carateres como a-z, desde que o início e o fim representem um intervalo contíguo de pontos de código.

regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false

As classes de carateres abreviadas tornam o padrão mais conciso. Por exemplo:

  • \d é a forma abreviada de [0-9] (qualquer algarismo)
  • \w é a forma abreviada de [A-Za-z0-9_] (qualquer caráter de "palavra")
  • \s é a forma abreviada de [ \t\r\n\f] (qualquer caráter de espaço em branco)

Quando usas uma classe de carateres abreviada fora de um sigil, tens de a escapar: "\\d"

Alternâncias

As alternâncias usam | como caráter especial para indicar a correspondência com um ou outro

regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true

Quantificadores

Os quantificadores permitem um padrão repetido na regex. Afetam o grupo que precede o quantificador.

  • {N, M} onde N é o número mínimo de repetições e M é o máximo
  • {N,} corresponde a N ou mais repetições
    • {0,} também pode ser escrito como *: corresponde a zero ou mais repetições
    • {1,} também pode ser escrito como +: corresponde a uma ou mais repetições
  • {,N} corresponde a até N repetições

Grupos

Os parênteses curvos () são usados para denotar grupos e capturas. Nalguns casos, o grupo também pode ser capturado para ser devolvido e usado. Em Elixir, estes podem ser nomeados ou não nomeados. As capturas são nomeadas acrescentando ?<name> depois do parêntese de abertura. Os grupos funcionam como uma única unidade, tal como quando são seguidos de quantificadores.

regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"

regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]

Âncoras

As âncoras servem para fixar a expressão regular ao início ou ao fim da string com que queremos corresponder:

  • ^ fixa ao início da string
  • $ fixa ao fim da string

Interpolação

Como o ~r é um atalho para "pattern" |> Regex.escape() |> Regex.compile!(), também podes usar interpolação de strings para construir dinamicamente um padrão de expressão regular:

anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true

Instruções

Tens a tarefa de escrever um serviço que ingere eventos. Cada evento tem uma data associada, mas reparas que estão a ser submetidos três formatos diferentes ao endpoint do teu serviço:

  • "01/01/1970"
  • "January 1, 1970"
  • "Thursday, January 1, 1970"

Consegues ver que existem algumas semelhanças entre si e decides escrever alguns padrões de expressões regulares que se podem compor.

1. Faz corresponder o dia, o mês e o ano de uma data

Implementa day/0, month/0 e year/0 para devolver um padrão de string que, quando compilado, corresponde aos componentes numéricos em "01/01/1970" (dd/mm/yyyy). O dia e o mês podem aparecer como 1 ou 01 (preenchidos à esquerda com zeros).

"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true

2. Faz corresponder o nome do dia da semana e o nome do mês do ano

Implementa day_names/0 e month_names/0 para devolver um padrão de string que, quando compilado, corresponde ao nome do dia da semana e ao nome do mês do ano, respetivamente.

"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true

3. Captura o dia, o mês e o ano

Implementa capture_day/0, capture_month/0, capture_year/0, capture_day_name/0, capture_month_name/0 para devolver um padrão de string que captura os respetivos componentes com os nomes: "day", "month", "year", "day_name", "month_name"

DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")

# => %{"month_name" => "December"}

4. Combina as capturas para capturar a data completa

Implementa capture_numeric_date/0, capture_month_name_date() e capture_day_month_name_date/0 para devolver um padrão de string que captura os componentes da parte 3 utilizando o respetivo formato de data:

  • data numérica - "01/01/1970"
  • data com nome do mês - "January 1, 1970"
  • data com nome do dia e do mês - "Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")

# => %{"day" => "01", "month" => "01", "year" => "1970"}

5. Restringe a captura para corresponder apenas à data

Implementa match_numeric_date/0, match_month_name_date/0 e match_day_month_name_date/0 para devolver uma expressão regular compilada que apenas corresponde à data e que também consegue capturar os componentes.

"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true

DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")

# => %{
#     "day" => "1",
#     "day_name" => "Thursday",
#     "month_name" => "January",
#     "year" => "1970"
#   }
Editar via GitHub A ligação abre numa nova janela ou separador
Elixir Exercism

Estás pronto para começar Analisador de datas?

Inscreve-te no Exercism para aprenderes e dominares Elixir com 58 conceitos168 exercícios, e mentoria humana real, tudo grátis.