St

Strings em R

2 exercícios

Sobre Strings

Contexto

Uma string é um pedaço de texto entre aspas.

mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"

Infelizmente, a terminologia pode ser confusa para programadores familiarizados com outras linguagens.

Embora o type seja character, o R não faz distinção entre uma única letra (chamada char em várias outras linguagens, mas não no R) e uma string longa. Ambas são um único item e podem estar entre aspas simples ' ' ou duplas " ", indistintamente.

O guia de estilo recomenda usar aspas duplas, exceto quando queres usar aspas duplas dentro da string sem as escapar.

str2 <- 'String with "quotes"'  # convenience format
str2
#> [1] "String with \"quotes\""    # standard format

Um character vector é, portanto, um vetor de strings, com cada string como um único elemento.

s <- c("some", "strings")
s
#> [1] "some"    "strings"
length(s)
#> [1] 2
length(s[1])  # the number of vector elements, not the string length
#> [1] 1

nchar(s[1])  # the number of characters in the string "some"
#> [1] 4

No exemplo acima, length(s[1]) é equivalente a length("some"): o número de elementos do vetor, não o comprimento da string.

Manipular uma string individual é, obviamente, possível, como acontece com nchar() no exemplo acima, mas requer funções específicas que serão discutidas mais abaixo.

Carateres de escape

Como vimos acima, alguns carateres de pontuação precisam de ser «escapados», sendo precedidos por uma barra invertida \.

A lista não será surpresa para a maioria dos programadores com experiência, uma vez que este tópico está razoavelmente bem padronizado entre linguagens.

Além das aspas (como mostrado acima), a lista inclui novas linhas \n, tabulações \t, barra invertida \\ e símbolos Unicode que começam por \U ou \u.

Para evitar usar muitos carateres de escape, é possível definir strings em bruto dentro de r"( )":

r"(raw "string")"
#> [1] "raw \"string\""

Para strings em bruto que contêm parênteses ( ), há muitas outras opções de sintaxe.

Locales

Qualquer versão do R atualmente suportada codifica strings em UTF-8, por isso a maioria das línguas do mundo pode ser suportada.

"आधुनिक मानक हिन्दी"
#> [1] "आधुनिक मानक हिन्दी"

Além disso, muitas funções do R têm em conta o locale e tentam seguir as convenções locais para a ordenação de strings, a conversão para maiúsculas/minúsculas, datas e horas, etc.

Este programa mostrará exemplos principalmente para inglês dos EUA: tanto porque isto é um padrão comum, como porque os autores do programa são residentes nos EUA.

Os locales são um tópico complexo, por isso procura documentação adaptada à tua própria situação.

Funções para strings

A maioria das coisas é possível no R base, mas muitos programadores consideram o suporte para manipulação de strings bastante limitado e confuso.

Felizmente, existem pacotes do tidyverse que são muito mais intuitivos, flexíveis e coerentes entre si.

R base

Ainda há muito código legado a usar funções de string ao estilo antigo. Isto inclui a maioria das soluções da comunidade no Exercism.

Além disso, para tarefas pequenas pode ser útil usar funções incorporadas em vez de importar uma biblioteca grande. Os responsáveis do percurso de R tendem a adotar esta abordagem ao escrever soluções exemplares para validar os exercícios no nosso repositório do GitHub.

Por isso, ainda vale a pena aprender um pouco sobre as abordagens antigas.

As funções abaixo são apresentadas na sua forma mais simples, mas a maioria também tem argumentos opcionais. Consulta a documentação para mais detalhes.

nchar

Devolve o número de carateres numa string.

nchar("string")
#> [1] 6

nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7

O tópico (complicado!) do que significa «caráter» está fora do nosso âmbito. Repara apenas em alguns pontos nos exemplos acima:

  • Apesar de implementadas em C internamente, as strings em R não terminam em \0 (o que acrescentaria 1 ao comprimento).
  • Os carateres Unicode multibyte são tratados como seria de esperar. «Hrōðgār» tem 7 carateres (também chamados «runes» ou «code points»), mas precisa de 10 bytes para armazenamento.

Tem atenção ao que passas ao nchar. Os resultados podem ainda ser imprevisíveis para valores que não sejam character, embora isto tenha melhorado nas versões recentes do R.

grep

Procura um padrão num vetor de strings e devolve os índices onde há correspondência.

grep("Jav", c("Java", "Python", "Javascript"))
#> [1] 1 3
paste e paste0

Combina um número arbitrário de strings numa única string.

Opcionalmente, podes especificar um separador para paste: a predefinição é um espaço.

paste0(v) é equivalente a paste(v, sep = "").

paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintf

Para um controlo mais fino sobre a montagem de strings, o R copia a função sprintf do C (e de várias linguagens posteriores: a Wikipedia lista cerca de 30).

r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimws

Os espaços em branco no início e/ou no fim podem ser removidos com trimws():

s <- "    messy string   "
trimws(s)
#> [1] "messy string"

Existe um parâmetro which = "left" (ou "right") para evitar remover de ambas as extremidades.

substr

Opera sobre uma única string e devolve uma substring entre dois limites (inclusive).

substr("Exercism", 5, 7)
#> [1] "cis"
strsplit

Divide uma string numa lista do R, com base numa string separadora. O separador é obrigatório (sem valor predefinido), mas pode ser uma string vazia.

strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R"      "Python" "Julia" 

strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"

As listas serão abordadas num Conceito separado. Se ainda não chegaste a essa parte do programa, por agora fica a saber que a função unlist() irá (neste caso) converter uma lista num vetor.

unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
regexpr e regexpr

Abreviatura de «expressão regular», estas funções procuram ocorrências de um padrão numa string e devolvem uma lista de dados (potencialmente) úteis. São mencionadas apenas por uma questão de completude: há opções melhores!

regexpr devolve apenas a primeira ocorrência, gregexpr devolve todas as ocorrências («g» de pesquisa «global»).

Como os nomes sugerem, o padrão pode ser uma expressão regular, que será abordada num Conceito posterior.

Advanced

Porque é que estas funções de string antigas se parecem com comandos de shell do Unix?

Embora o R tenha sido criado em 1993, foi concebido para ser amplamente compatível com a linguagem S.

A linguagem S foi concebida nos Bell Labs, tendo aparecido pela primeira vez em 1976. Os Bell Labs também foram responsáveis pelo Unix (desde 1969) e pela linguagem C (1972).

Uma bela equipa daquela era muito anterior! Só podemos supor que falavam uns com os outros e trocavam ideias.

Tidyverse

Sendo generosos, podemos dizer que as funções de string ao estilo antigo não são o melhor que o R tem para oferecer (há visões menos generosas).

Para melhorar a situação, a biblioteca stringr está em desenvolvimento desde 2009 e é agora uma parte fundamental da coleção Tidyverse.

Importa-a para o teu namespace com library(stringr) ou library(tidyverse).

Algumas funcionalidades fundamentais incluem:

  • Todos os nomes de funções têm um prefixo str_ e tentam deixar claro o propósito.
  • A ordem dos argumentos tenta ser coerente entre si, com os dados de entrada como primeiro argumento.
  • Os valores de entrada podem ir de uma única string a estruturas de dados complexas. As funções tentarão adaptar-se e fazer o mais sensato.

Como todos os pacotes do Tidyverse, o stringr tem boa documentação, normalmente escrita pelo(s) autor(es) do pacote.

Há dezenas de funções disponíveis no stringr, e têm frequentemente uma variedade de argumentos opcionais. Isto pode ser um pouco esmagador ao início, por isso a documentação é a tua amiga.

Pensa no que queres colocar (uma única string? um vetor? algo mais complexo?) e no que queres obter.

Provavelmente há uma forma fácil de fazer o que quiseres, se conseguires encontrar a função certa e os argumentos certos.

Digressão: uma introdução rápida aos pipes

A utilização de pipes |> para ligar funções será discutida com mais detalhe noutras partes do programa.

Por agora, é útil saberes que:

  • Um resultado à esquerda do pipe torna-se o primeiro argumento na função à direita (em que «direita» ignora quebras de linha opcionais).
  • Quaisquer argumentos restantes podem ser incluídos na chamada à função.
  • No R, o pipe tem de terminar numa chamada à função, e não apenas num nome, por isso os parênteses são sempre obrigatórios (isto difere de algumas outras linguagens).

O exemplo abaixo ilustra a sintaxe. As funções individuais serão discutidas mais adiante.

library(stringr)

"Monday-25-February" |> 
  str_split("-") |>     
  unlist() |>               # [1] "Monday"   "25"       "February"
  str_sub(1, 3) |>          # [1] "Mon" "25"  "Feb"
  str_to_upper() |>         # [1] "MON" "25"  "FEB"
  str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
Gerir comprimentos

str_length conta code points (semelhante a nchars).

Várias outras funções manipulam espaços em branco, incluindo str_trim() e str_squish(). Esta é frequentemente uma tarefa de limpeza vital no início de qualquer projeto de ciência de dados.

# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R"))           #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right")  # add spaces if necessary
#> [1] "Julia   " "Python  " "R       "

str_trim("Julia    Python    R    ")   # remove leading & trailing whitespace
#> [1] "Julia    Python    R"
str_squish("Julia   Python  R    ")  # trim, collapse multiple spaces 
#> [1] "Julia Python R"
Subconjuntos de strings

A função str_sub(string, start, end) devolve a substring de start a end (inclusive), enquanto str_sub_all() pode operar sobre múltiplas strings.

Há muita flexibilidade. start e end assumem, por predefinição, o primeiro e o último caráter, respetivamente. Podem ser vetores de índices. Em agradável contraste com a indexação de vetores, os valores negativos contam a partir do fim da string, ao estilo do Python.

s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab"  "de"  "fgh"

s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"

# str_sub_all takes vector input, gives list output
str_sub_all(c(s, "ijklmnop"), c(1, 4, 6), c(2, 5, -1))
[[1]]
#> [1] "ab"  "de"  "fgh"

[[2]]
#> [1] "ij"  "lm"  "nop"
Unir strings

A função str_flatten converte um vetor de carateres numa única string. O argumento collapse é inserido entre cada elemento e a predefinição é "".

c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"

A função str_c converte múltiplos vetores de carateres num único vetor de carateres, com reciclagem conforme necessário.

str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"

A função str_glue traz interpolação simples de strings ao R. As expressões dentro de chavetas { } são interpretadas e a respetiva representação textual é substituída.

Se forem fornecidas várias strings de expressões, também serão unidas como em str_flatten. As strings também podem, opcionalmente, ser limpas de espaços em branco no início e no fim.

r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631

Embora str_glue seja flexível (e bastante complicado), há um leque ainda mais amplo de possibilidades na biblioteca subjacente glue.

Dividir strings

Um uso muito comum do R é transformar dados desorganizados num formato consistente para análise. Para ajudar nisto, há várias formas de dividir texto em substrings.

A função str_split_1 é a mais simples, convertendo uma única string num vetor de partes, divididas num determinado padrão especificado (não há padrão predefinido).

s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R"      "Julia"  "Python"

A função str_split_i recebe um vetor de carateres e um índice n, devolvendo um vetor com a n-ésima substring de cada string de entrada.

s2 <- c(s, "C Kotlin, F#")  #> [1] "R Julia Python" "C Kotlin, F#"
str_split_i(s2, " ", 2)  # 2nd element of each
#> [1] "Julia"   "Kotlin,"

A função str_split recebe um vetor de carateres e devolve uma lista de vetores. Há um Conceito de Listas no nosso programa que explica o resultado.

str_split(s2, " ")
[[1]]
#> [1] "R"      "Julia"  "Python"

[[2]]
#> [1] "C"       "Kotlin," "F#"     

Há também a função str_split_fixed, que devolve uma matriz. As matrizes serão abordadas num Conceito posterior.

Comparar strings

Para comparar a string inteira, str_equal() pode usar várias definições de «igualdade». No caso mais simples, a predefinição distingue maiúsculas de minúsculas, mas isso pode ser alterado. Os locales e o Unicode criam muitas mais possibilidades.

str_equal("Odin", "odin")
#> [1] FALSE
str_equal("Odin", "odin", ignore_case = TRUE)
#> [1] TRUE

Há muitas outras funções, que serão discutidas com mais detalhe no Conceito de Expressões Regulares.

Modificar strings

Algumas línguas (principalmente europeias) distinguem entre letras MAIÚSCULAS e minúsculas. Há um conjunto de funções do stringr para converter entre estas, de formas adequadas ao locale:

  • str_to_lower()
  • str_to_upper()
  • str_to_title() : primeira letra de cada palavra em maiúscula
  • str_to_sentence() : primeira letra da frase em maiúscula
str_to_upper("elixir")    # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά")  # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"

str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"

Para substituição de substrings com base na correspondência de padrões, str_replace() altera a primeira ocorrência do padrão e str_replace_all() altera todas as ocorrências.

Para substituição de substrings com base na indexação de carateres, há uma forma de atribuição de str_sub().

 s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"
Diversos

A função str_dup cria duplicados e str_unique remove duplicados.

Por predefinição, str_dup não tem separador, mas é possível especificar um.

str_dup("xyz", 4, sep = ", ")
#> [1] "xyz, xyz, xyz, xyz"

str_unique é uma extensão de unique(). Ambas removem entradas duplicadas de um vetor, mas a função do stringr também consegue lidar com a distinção entre maiúsculas e minúsculas e várias correspondências específicas do locale.

inp <- c("some", "strings", "Some", "Strings")
str_unique(inp)
#> [1] "some"    "strings" "Some"    "Strings"
str_unique(inp, ignore_case = TRUE)
#> [1] "some"    "strings"
Advanced

A biblioteca stringr é baseada na biblioteca stringi de funções de mais baixo nível.

Se tiveres necessidades específicas que ainda não foram abordadas acima, então:

  1. Consulta a documentação do stringr, que cobre mais opções do que conseguimos mencionar mesmo neste longo documento de Conceito.
  2. Consulta a documentação do stringi, que descreve muito mais funcionalidades.
Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Strings

A prática está bloqueada

Desbloqueia mais 1 exercício para praticares Strings