St

Strings em Julia

7 exercícios

Sobre Strings

Uma String em Julia é uma sequência ordenada de caracteres. Como o manual observa: "É claro que o verdadeiro problema surge quando alguém pergunta o que é um caractere."

Boa parte dessa complexidade será abordada com mais detalhes no Conceito Chars, mas, em resumo:

  • Todas as strings em Julia são Unicode.
  • Strings que também são ASCII (letras inglesas de A a Z em maiúsculas e minúsculas, dígitos e alguns caracteres de pontuação) são fáceis de manipular.
  • A maioria dos outros sistemas de escrita do mundo é possível de manipular, mas exige mais cuidado.

Literais de string

Em geral, strings são delimitadas por aspas duplas " ". Aspas simples ' ' são usadas apenas para Chars.

Também é possível usar aspas duplas triplas """ """, o que permite usar " dentro da string sem escape. Mais importante ainda: a indentação extra indesejada é removida de strings com várias linhas.

julia> """
       Multiline
       String
       """
"Multiline\nString\n"

Caracteres de escape

Como a maioria das linguagens a partir do C, Julia usa uma barra invertida \ para escape:

  1. Para inserir caracteres não imprimíveis, como a quebra de linha \n do exemplo anterior.
  2. Para proteger caracteres que, de outra forma, teriam um significado especial, como \$ (veja abaixo).

Iteração

Uma string é uma coleção iterável, então um laço for ... in funciona sem precisar converter para um vetor de caracteres.

julia> s = "Julia"
"Julia"

# admittedly, this is a silly example
julia> for c in s; print(c, "; "); end
J; u; l; i; a; 

Iterar assim tem a vantagem de que Julia trata corretamente os limites de caractere, mesmo em strings não ASCII. A importância disso ficará mais clara na próxima seção.

Indexação

Em princípio, strings podem ser indexadas exatamente como vetores:

julia> s[1], s[5]
('J', 'a')

Isso claramente funciona para uma string ASCII como "Julia", mas isso ignora a maioria dos idiomas do mundo.

Considere um dos caracteres de Beowulf, escrito em inglês antigo há cerca de 15 séculos.

"Hrōðgār" claramente tem 7 caracteres, mas nem todos são ASCII.

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> isascii(king)
false

# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
 'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
 'ō': Unicode U+014D (category Ll: Letter, lowercase)
 'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
 'ā': Unicode U+0101 (category Ll: Letter, lowercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)

Indexar uma string assim começa bem, mas em algum momento quebra:

julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)

julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'

O problema é que indexar assim conta bytes, e em strings não ASCII os bytes não têm uma relação de 1:1 com os caracteres. Um caractere Unicode/UTF-8 pode precisar de até 4 bytes para ser representado, e o Conceito Chars vai explorar isso mais a fundo.

Construindo strings

Concatenação

Muitas linguagens usam + como operador de concatenação de strings, mas Julia não. Pelo menos a mensagem de erro é útil.

julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with * 

julia> "s1 " * "s2"
"s1 s2"

A função string() concatena um número arbitrário de strings.

julia> string("lots ", "of ", "bits")
"lots of bits"

A função join() faz o mesmo e também aceita um vetor de strings, concatenando-as com um separador dado (por padrão, "").

julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"

Observe que strings são imutáveis e que a concatenação envolve cópia. Por isso, concatenações repetidas dentro de um laço são ineficientes; é melhor reunir os fragmentos em um vetor e juntar todos eles no final.

Repetição

Simples:

julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"

Interpolação

Muitas linguagens têm uma forma de inserir valores calculados em strings, embora haja uma enorme divergência sobre qual seria a melhor sintaxe.

Em Julia, valores interpolados são precedidos por $.

julia> myvar = 42
42

julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"

julia> r = 5.3
5.3

julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"

Os parênteses são obrigatórios quando omiti-los seria ambíguo, mas opcionais para um único identificador seguido de espaço em branco.

Para incluir um $ na string, use o escape \$.

Observe a limitação de que a interpolação, no momento, não permite formatação, como o número de casas decimais a exibir.

Alternativas incluem:

  • Envolver o cálculo em uma função round().
  • Fora do test runner do Exercism, o pacote PyFormattedStrings emula as f-strings do Python.
  • A macro @sprintf() é descrita abaixo.

@sprintf()

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

Em Julia, isso é implementado como uma macro dentro do módulo Printf, daí o prefixo @.

julia> using Printf

julia> r = 5.3
5.3

julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"

Funções de string

Para simplificar, esta seção vai se concentrar nas funções que retornam uma nova string e deixam a entrada inalterada. Muitas têm uma equivalente, com sufixo !, que modifica a entrada no local.

Comprimento

Qual é o comprimento de uma string? Às vezes, depende de como você está contando.

O caso simples são as strings ASCII, em que cada caractere ocupa 1 byte.

julia> string_asc = "Julia"
"Julia"

julia> length(string_asc)
5

julia> sizeof(string_asc)
5

A função length() retorna o número de caracteres, e a função sizeof() o número de bytes.

Essa distinção se torna importante com outros conjuntos de caracteres:

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> sizeof(king)
10

Divisão

Uma operação comum em programação é pegar uma string longa e dividi-la em pedaços, com base em uma string separadora de um ou mais caracteres.

De modo geral, usamos a função split(). O separador, por padrão, é qualquer espaço em branco, mas é possível especificar outros.

julia> split("my little string")
3-element Vector{SubString{String}}:
 "my"
 "little"
 "string"

julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
 "My"
 "snake"
 "case"
 "string"

Esses exemplos dividem uma string em strings menores. Dividir uma string em caracteres é uma operação diferente.

julia> collect("input string")
12-element Vector{Char}:
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
 'p': ASCII/Unicode U+0070 (category Ll: Letter, lowercase)
 'u': ASCII/Unicode U+0075 (category Ll: Letter, lowercase)
 't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
 ' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
 's': ASCII/Unicode U+0073 (category Ll: Letter, lowercase)
 't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)

Localizar e substituir

Uma string contém uma determinada substring? Curiosamente, há duas funções para testar isso: occursin() e contains() diferem apenas na ordem dos argumentos.

julia> needle = "Julia"
"Julia"

julia> haystack = "Python, Julia and R"
"Python, Julia and R"

julia> occursin(needle, haystack)
true

julia> contains(haystack, needle)
true

Para ser mais específico quanto à posição:

julia> startswith(haystack, "Python")
true

julia> endswith(haystack, "Python")
false

Para obter os índices início:fim de uma substring, temos várias funções:

julia> findfirst(needle, haystack)
9:13

Para substituir substrings, liste as mudanças usando a sintaxe x => y.

julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"

Desde o Julia 1.7, a função replace aceita um número arbitrário de mudanças, com a garantia de que nenhum caractere será alterado mais de uma vez durante a substituição.

A sintaxe x => y é chamada de pair, um tipo importante em Julia que é abordado com mais detalhes no Conceito Dicts and Pairs.

Observação: Para simplificar, todos os exemplos acima usam literais de string. Muitas dessas funções são mais gerais e também funcionam com Expressões Regulares. Voltaremos a isso em um Conceito posterior.

Espaços em branco

Muitas vezes precisamos remover espaços em branco do início e/ou do fim de uma string.

As funções para isso são lstrip() (à esquerda), rstrip() (à direita) e strip() (ambos).

julia> strip("\n\t  useful_bit\n\n")
"useful_bit"

Alterar maiúsculas e minúsculas

Essas funções têm nomes bastante autoexplicativos:

julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "

julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "

julia> lowercase(s)
"abcd abcd abcd abcd abcd "

julia> titlecase(s)  # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "

Conversão entre strings e números

Converter números em sua representação como string, e vice-versa, costuma ser útil.

Converter número em string é simples, embora uma base diferente do padrão 10 precise de um parâmetro extra:

julia> string(42)
"42"

julia> string(42, base=16)
"2a"

julia> string(42, base=2)
"101010"

Converter uma string em um valor numérico é um pouco mais complicado, com mais chances de erro. Especificar o tipo numérico de destino é obrigatório. Especificar a base é opcional e o padrão é 10.

julia> parse(Int, "42")
42

julia> parse(Float64, "42")
42.0

julia> parse(Int, "42?")
ERROR: ArgumentError: invalid base 10 digit '?' in "42?"

julia> parse(Int, "42 ")  # whitespace may be tolerated
42

Strings especiais

Às vezes é útil suprimir a interpolação e o escape usuais dentro de strings. Para isso, coloque raw antes da aspa de abertura.

julia> x = 10
10

julia> "x = $x if we use \$ for interpolation"
"x = 10 if we use \$ for interpolation"

julia> raw"x = $x if we use \$ for interpolation"
"x = \$x if we use \\\$ for interpolation"

As Expressões Regulares têm uma sintaxe complicada própria, e Julia oferece dois tipos de string úteis: r" " para a Regex e s" " para trabalhar com fragmentos capturados.

Vários outros tipos de string especiais são menos comuns na programação no estilo do Exercism, embora sejam muito usados na criação de bibliotecas.

  • Strings de versão semântica v"x.y.z" lidam com a numeração major.minor.patch das versões de software.
  • Literais de array de bytes b" " contornam os limites de caractere Unicode e operam no nível de byte.
Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Strings

A prática está bloqueada

Desbloqueie mais 2 exercícios para praticar Strings