St

Strings em Julia

7 exercícios

Sobre Strings

Uma String em Julia é uma sequência ordenada de carateres. Como o manual indica: «Claro que o verdadeiro problema surge quando alguém pergunta o que é um caráter.»

Grande parte desta complexidade será abordada com mais detalhe no Conceito Chars, mas, em resumo:

  • Todas as strings em Julia são Unicode.
  • As strings que também são ASCII (letras inglesas de A a Z, maiúsculas e minúsculas, algarismos e alguns carateres de pontuação) são fáceis de trabalhar.
  • Trabalhar com a maioria dos outros sistemas de escrita do mundo é possível, mas requer mais cuidado.

Literais de string

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

Também é possível usar aspas duplas triplas """ """, o que permite usar " dentro da string sem escapar. Mais importante ainda, a indentação extra indesejada é removida das strings multilinha.

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

Escapar carateres

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

  1. Para introduzir carateres não imprimíveis, como a nova linha \n no exemplo anterior.
  2. Para proteger carateres que, de outra forma, teriam um significado especial, como \$ (ver abaixo).

Iteração

Uma string é uma coleção iterável, por isso um ciclo for ... in funciona sem ser preciso converter para um vetor de carateres.

julia> s = "Julia"
"Julia"

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

Iterar desta forma tem a vantagem de Julia tratar corretamente os limites entre carateres, mesmo para strings não ASCII. A importância disto ficará mais clara na secção seguinte.

Indexação

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

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

Isto funciona claramente para uma string ASCII como "Julia", mas ignora a maioria das línguas do mundo.

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

"Hrōðgār" tem claramente 7 carateres, 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 desta forma começa bem, mas a certa altura falha:

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 desta forma conta bytes, que, para strings não ASCII, não têm uma relação de 1:1 com os carateres. Um caráter Unicode/UTF-8 pode precisar de até 4 bytes para ser representado, e o Conceito Chars vai explorar isto mais aprofundadamente.

Construção de 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 e concatena-as com um separador indicado (por omissão, "").

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

Repara que as strings são imutáveis e a concatenação envolve cópia. Por isso, concatenações repetidas dentro de um ciclo são ineficientes; é melhor reunir os fragmentos num vetor e juntá-los todos no fim.

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 discordância extraordinária sobre a melhor sintaxe a usar.

Em Julia, os 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 tornaria a expressão ambígua, mas são opcionais para um único identificador seguido de espaço em branco.

Para incluir um $ na string, escapa-o como \$.

Nota que a interpolação, atualmente, não permite formatação, como o número de casas decimais a apresentar.

As soluções alternativas incluem:

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

@sprintf()

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

Em Julia, isto é 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 secção vai concentrar-se nas funções que devolvem uma nova string e deixam a string de entrada inalterada. Muitas têm um equivalente, com o sufixo !, que modifica a string de entrada no local.

Comprimento

Qual é o comprimento de uma string? Por vezes, depende de como estás a contar.

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

julia> string_asc = "Julia"
"Julia"

julia> length(string_asc)
5

julia> sizeof(string_asc)
5

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

Esta distinção torna-se importante com outros conjuntos de carateres:

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 numa string longa e dividi-la em partes, com base numa string separadora de um ou mais carateres.

De um modo geral, usamos a função split(). O separador, por omissão, é qualquer espaço em branco, mas podem indicar-se 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"

Estes exemplos dividem uma string em strings mais pequenas. Dividir uma string em carateres é 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 substring especificada? Curiosamente, há duas funções para testar isto: occursin() e contains() diferem apenas na ordem dos seus 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 de início:fim de uma substring, temos várias funções:

julia> findfirst(needle, haystack)
9:13

Para substituir substrings, lista as alterações usando a sintaxe x => y.

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

Desde Julia 1.7, a função replace pode receber um número arbitrário de alterações, com a garantia de que nenhum caráter será alterado mais de uma vez durante a substituição.

A sintaxe x => y chama-se pair, um tipo importante em Julia que é abordado com mais detalhe no Conceito Dicts and Pairs.

Nota: Para simplificar, todos os exemplos acima usam literais de string. Muitas das funções são mais gerais e também funcionam com expressões regulares. Voltaremos a isto num Conceito mais adiante.

Espaços em branco

Muitas vezes, precisamos de remover espaço em branco no início e/ou no fim de uma string.

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

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

Alterar maiúsculas e minúsculas

Estas 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 "

Análise

Converter números na sua representação em string e vice-versa é muitas vezes útil.

Converter números em strings é simples, embora uma base diferente da predefinida 10 precise de um parâmetro extra:

julia> string(42)
"42"

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

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

Analisar uma string para obter um valor numérico é um pouco mais complicado e tem mais potencial de erro. Especificar o tipo numérico de destino é obrigatório. Especificar a base é opcional e, por omissã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

Por vezes, é útil suprimir a interpolação e o escape habituais dentro das strings. Para isso, coloca 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 a sua própria sintaxe complicada, e Julia fornece dois tipos de string úteis: r" " para a regex e s" " para trabalhar com fragmentos capturados.

Várias outras strings especiais são menos comuns na programação ao estilo do Exercism, embora sejam amplamente usadas na criação de bibliotecas.

  • As strings de versão semântica v"x.y.z" tratam da numeração major.minor.patch para lançamentos de software.
  • Os literais de array de bytes b" " ignoram os limites de carateres Unicode e operam ao nível do byte.
Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Strings

A prática está bloqueada

Desbloqueia mais 2 exercícios para praticares Strings