Percursos
/
R
R
/
Programa
/
Expressões regulares
Ex

Expressões regulares em R

1 exercício

Sobre Expressões regulares

As expressões regulares são uma forma altamente versátil de fazer correspondência de padrões em strings, usando uma linguagem específica de domínio (DSL) concebida para esse efeito.

Advanced

Tal como muitas outras linguagens de programação, o R não tenta implementar a sua própria biblioteca de expressões regulares. Em vez disso, envolve o motor de expressões regulares ICU. O ICU é semelhante à popular biblioteca PCRE2 usada pela maioria das linguagens mais recentes, embora alguns casos limite sejam tratados de forma diferente.

Muitas funcionalidades das expressões regulares estão incorporadas no R base, e podes ver exemplos em código mais antigo.

Para código novo, é vivamente recomendado usar a biblioteca stringr.

Como referido no conceito Strings, o stringr tem boa documentação, normalmente escrita pelos autores do pacote.

Note

Este programa de R parte do princípio de que já estás familiarizado com a sintaxe básica das expressões regulares. Vamos concentrar-nos apenas nas funcionalidades específicas do R.

Abaixo estão listados alguns recursos para refrescar os teus conhecimentos de expressões regulares.

Muitas das funções já abordadas no conceito Strings foram concebidas para fazer pesquisas com expressões regulares de forma padrão.

Mantivemos o conceito Strings relativamente simples, fazendo correspondência apenas com literais de string, mas podes usar qualquer padrão de expressão regular em vez disso.

Formalmente, uma expressão regular é criada em R chamando regex(). Por conveniência, as funções do stringr tratam qualquer string de padrão como uma expressão regular. No exemplo abaixo, o primeiro str_replace_all() é açúcar sintático para a segunda versão:

input <- "Some input string"

# "\\s" matches any whitespace character
input |> str_replace_all("\\s", "_")
[1] "Some_input_string"

input |> str_replace_all(regex("\\s"), "_")
[1] "Some_input_string"

A cheatsheet é um guia útil: todas as funções com um argumento pattern (impresso a vermelho) aceitam uma expressão regular, e a página 2 resume a sintaxe das expressões regulares.

Repara que a função regex() ainda pode ser importante se precisares de alterar os valores predefinidos, como a distinção entre maiúsculas e minúsculas ou a localidade.

# default is case sensitive
"A Mixed Assortment" |> 
    str_replace_all(regex("[aeiou]", ignore_case = TRUE), "*")
#> [1] "* M*x*d *ss*rtm*nt"

Muitos códigos de expressões regulares incluem uma barra invertida, como o \s usado acima. Nas strings normais do R, o \ tem de ser escapado duplicando-o, daí "\\s".

As barras invertidas podem multiplicar-se e tornar-se confusas, por isso pode ajudar usar raw strings em casos complexos.

Por predefinição, a sintaxe para definir uma raw string é r"( )", mas são permitidos outros delimitadores. Como os parênteses curvos ( ) e os parênteses retos [ ] são ambos comuns na sintaxe das expressões regulares (para grupos de captura e grupos de opções), as chavetas { } podem ser uma boa escolha.

input |> str_replace_all(r"{\s\w}", "__")
#> [1] "Some__nput__tring"

Detetar correspondências

Aqui estão algumas perguntas e as funções que podem ajudar a responder-lhes.

Alguns exemplos usam fruit, um vetor de 80 nomes de frutos incluído no stringr para praticar.

Uma string contém uma correspondência? str_detect(), ou str_starts(), str_ends() para evitar usar as âncoras ^ ou $.

# berries begining with b
fruit |> str_detect("^b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

# alternative syntax
fruit |> str_starts("b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

Devolver apenas as strings que contêm uma correspondência? Usa str_subset() para obteres as strings e str_which() para obteres os índices.

fruit |> str_subset("a...e")
[1] "apple"       "blackberry"  "mandarine"   "nectarine"   "pineapple"   "pomegranate" "raspberry"   "salal berry"
> fruit |> str_which("a...e")
[1]  1  7 48 51 62 64 70 73

Quantas correspondências há em cada string? str_count()

# count vowels
fruit |> str_count("[aeiou]") |> head(10)
#> [1] 2 3 4 3 3 2 2 3 5 3

Onde está a correspondência? str_view() para uso interativo, str_locate() ou str_locate_all() de forma mais geral.

# str_view only shows matching elements, with match in < > delimiters
fruit |> str_view("a...e")
 [1] │ <apple>
 [7] │ bl<ackbe>rry
[48] │ mand<arine>
[51] │ nect<arine>
[62] │ pine<apple>
[64] │ pomegr<anate>
[70] │ r<aspbe>rry
[73] │ sal<al be>rry

# str_locate returns all elements
fruit |> str_locate("a...e") |> head(10)
      start end
 [1,]     1   5
 [2,]    NA  NA
 [3,]    NA  NA
 [4,]    NA  NA
 [5,]    NA  NA
 [6,]    NA  NA
 [7,]     3   7
 [8,]    NA  NA
 [9,]    NA  NA
[10,]    NA  NA

Capturas

Frequentemente, queremos saber o que corresponde. Isto consegue-se incluindo grupos de captura entre parênteses dentro da expressão regular.

É possível fazer referência a capturas dentro da mesma expressão regular, usando \1, \2, etc. Por exemplo, encontrar pares de letras repetidos:

fruit |> str_subset("(..)\\1")
[1] "banana"      "coconut"     "cucumber"    "jujube"      "papaya"      "salal berry"

Com uma sintaxe semelhante, as capturas podem ser usadas em strings de substituição:

# double to triple
fruit |> 
  str_subset("(..)\\1") |> 
  str_replace("(..)\\1", "\\1\\1\\1")
[1] "bananana"      "cococonut"     "cucucumber"    "jujujube"      "papapaya"      "salalal berry"

Recolhe correspondências para processamento posterior usando a função str_match() para apenas a primeira correspondência, ou str_match_all() para todas as correspondências.

Os grupos de captura tendem a produzir resultados complicados em qualquer linguagem. A abordagem do R (que também é vetorizada, como desafio adicional) é gerar uma matriz a partir de str_match(), com uma linha por string de entrada, a coluna 1 a conter a string correspondida completa e as restantes colunas a conterem os vários grupos de captura.

Usar grupos com nome pode ser uma vantagem, já que se tornam nomes de colunas na matriz de resultados.

recipe <- "dissolve 25g sugar in 200ml water"
recipe |> str_match("(\\d+g) .* (\\d+ml)")
     [,1]                 [,2]  [,3]   
[1,] "25g sugar in 200ml" "25g" "200ml"

# use named capture groups
recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")
                          wt    vol    
[1,] "25g sugar in 200ml" "25g" "200ml"

Não falámos muito sobre matrizes neste programa, embora exista um documento de conceito a que podes aceder. As matrizes não se dão bem com o ecossistema Tidyverse, apesar de fazerem parte do R base há muito tempo.

Em resumo: trata as matrizes como vetores com 2 índices, pela ordem [rows, cols]. Deixa qualquer um dos índices em branco para obteres a linha ou a coluna completa.

Quando obténs uma única linha r com m[r,] ou uma única coluna c com m[, c], o valor devolvido é achatado para um vetor por predefinição.

> amounts <- recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")

# get all of row 1, as a named vector
> amt1 <- amounts[1,]
> amt1
                                       wt                  vol 
"25g sugar in 200ml"                "25g"              "200ml" 

# get a vector element
amt1["wt"] |> unname()
[1] "25g"

Inevitavelmente, str_match_all() produz resultados com ainda mais estrutura:

  • O resultado é uma lista, com um elemento por cada string de entrada.
  • Se houver apenas uma string de entrada, desfaz a lista do resultado com a indexação [[1]] como descrito no conceito Lists.
  • Cada elemento é uma matriz, com as colunas como em match().
  • Cada linha da matriz é uma captura bem-sucedida.
mix <- "Mix 200g of sugar, 180g of flour and 150g of butter"

# raw output
> str_match_all(mix, "(\\d+)g")
[[1]]
     [,1]   [,2] 
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"

# we just want the first list element
> str_match_all(mix, "(\\d+)g")[[1]]
     [,1]   [,2] 
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"

# captures are in column 2, so get that as a vector
> str_match_all(mix, "(\\d+)g")[[1]][,2]
[1] "200" "180" "150"

Claro que as correspondências podem falhar. O resultado será então o valor especial NA. Prepara-te para testar isto, como descrito no conceito Nothingness.

# failed match
recipe |> str_match("(not here)")
     [,1] [,2]
[1,] NA   NA  
Note

R for Data science observa:

O str_match() devolve uma matriz, por isso não é particularmente fácil de trabalhar.

Com a nota de rodapé:

Principalmente porque nunca discutimos matrizes neste livro!

Um dos autores do livro também escreveu a biblioteca stringr e está implicitamente a criticar o seu próprio trabalho.

Será interessante ver como a implementação desta função evolui em versões futuras.

Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Expressões regulares