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:
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"
Como a maioria das linguagens a partir de C, Julia usa uma barra invertida \ para escapar:
\n no exemplo anterior.\$ (ver abaixo).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.
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.
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.
Simples:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
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:
round().@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"
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.
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
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)
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.
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"
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 "
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
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.
v"x.y.z" tratam da numeração major.minor.patch para lançamentos de software.b" " ignoram os limites de carateres Unicode e operam ao nível do byte.