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:
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"
Como a maioria das linguagens a partir do C, Julia usa uma barra invertida \ para escape:
\n do exemplo anterior.\$ (veja abaixo).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.
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.
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.
Simples:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
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:
round().@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"
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.
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
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)
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.
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"
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 "
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
À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.
v"x.y.z" lidam com a numeração major.minor.patch das versões de software.b" " contornam os limites de caractere Unicode e operam no nível de byte.