En Julia, un String es una cadena de caracteres: una secuencia ordenada de caracteres. Como señala el manual: «Claro que el verdadero problema surge cuando uno se pregunta qué es un carácter».
Gran parte de esta complejidad se tratará con más detalle en el concepto Chars, pero, brevemente:
En general, los strings se escriben entre comillas dobles " ".
Las comillas simples ' ' solo se usan para los Chars.
También es posible usar comillas dobles triples """ """, que permiten usar " dentro del string sin necesidad de escaparlo.
Y, lo que es más importante, se elimina la indentación adicional no deseada de los strings de varias líneas.
julia> """
Multiline
String
"""
"Multiline\nString\n"
Como la mayoría de los lenguajes desde C en adelante, Julia usa una barra invertida \ para escapar:
\n del ejemplo anterior.\$ (ver más abajo).Un string es una colección iterable, así que un bucle for ... in funciona sin necesidad de convertir a un vector 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 de esta forma tiene la ventaja de que Julia gestiona correctamente los límites entre caracteres, incluso en strings que no son ASCII. La importancia de esto quedará más clara en la sección siguiente.
En principio, los strings se pueden indexar exactamente igual que los vectores:
julia> s[1], s[5]
('J', 'a')
Esto funciona claramente con un string ASCII como «Julia», pero ignora la mayoría de los idiomas del mundo.
Fíjate en uno de los caracteres de Beowulf, escrito en inglés antiguo hace unos 15 siglos.
«Hrōðgār» tiene claramente 7 caracteres, pero no todos son 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 un string de esta forma empieza bien, pero en algún momento falla:
julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)
julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'
El problema es que indexar así cuenta bytes, que en los strings que no son ASCII no guardan una relación 1:1 con los caracteres.
Un carácter Unicode/UTF-8 puede necesitar hasta 4 bytes para representarse, y el concepto Chars lo explorará con más detalle.
Muchos lenguajes usan + como operador de concatenación de strings, pero Julia no.
Al menos, el mensaje de error es ú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"
La función string() concatena un número arbitrario de strings.
julia> string("lots ", "of ", "bits")
"lots of bits"
La función join() hace lo mismo y además acepta un vector de strings y los concatena con un separador dado (por defecto, "").
julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"
Ten en cuenta que los strings son inmutables, y concatenar implica copiar. Por tanto, las concatenaciones repetidas dentro de un bucle son ineficientes y es mejor reunir los fragmentos en un vector y unirlos todos al final.
Sencillo:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
Muchos lenguajes tienen alguna forma de insertar valores calculados en los strings, aunque hay un desacuerdo enorme sobre cuál es la mejor sintaxis.
En Julia, los valores interpolados van precedidos de $.
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"
Los paréntesis son obligatorios cuando omitirlos sería ambiguo, pero opcionales para un único identificador seguido de un espacio.
Para incluir un $ en el string, escápalo como \$.
Ten en cuenta la limitación de que la interpolación no permite actualmente dar formato, como el número de decimales que se muestran.
Algunas soluciones alternativas son:
round().@sprintf() se describe más abajo.@sprintf()Para tener un control más preciso sobre el ensamblado de strings, Julia copia la función sprintf de C (y de varios lenguajes posteriores: Wikipedia enumera unos 30).
En Julia, esto se implementa como una macro dentro del módulo Printf, de ahí el prefijo @.
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"
Por simplificar, esta sección se centrará en las funciones que devuelven un string nuevo y dejan la entrada sin cambios.
Muchas tienen una equivalente, con el sufijo !, que modifica la entrada en el mismo lugar.
¿Cuánto mide un string? A veces depende de cómo lo cuentes.
El caso sencillo son los strings ASCII, en los que cada carácter ocupa 1 byte.
julia> string_asc = "Julia"
"Julia"
julia> length(string_asc)
5
julia> sizeof(string_asc)
5
La función length() devuelve el número de caracteres, y la función sizeof(), el número de bytes.
Esta distinción se vuelve importante con otros conjuntos de caracteres:
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> sizeof(king)
10
Una operación habitual en programación es tomar un string largo y dividirlo en trozos a partir de un string separador de uno o más caracteres.
Lo más habitual es usar la función split().
El separador es por defecto cualquier espacio en blanco, pero se pueden especificar otros.
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"
Estos ejemplos dividen un string en strings más pequeños. Dividir un string en caracteres es una operación distinta.
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)
¿Contiene un string un substring determinado?
Curiosamente, hay dos funciones para comprobarlo: occursin() y contains() solo se diferencian en el orden de sus 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 más concretos con la posición:
julia> startswith(haystack, "Python")
true
julia> endswith(haystack, "Python")
false
Para obtener los índices de inicio:fin de un substring, tenemos varias funciones:
julia> findfirst(needle, haystack)
9:13
Para reemplazar substrings, indica los cambios con la sintaxis x => y.
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
Desde Julia 1.7, la función replace puede aceptar un número arbitrario de cambios, con la garantía de que ningún carácter se cambiará más de una vez durante el reemplazo.
La sintaxis x => y se llama pair, un tipo importante en Julia que se trata con más detalle en el concepto Dicts and Pairs.
Nota: por simplificar, todos los ejemplos anteriores usan literales de string. Muchas de las funciones son más generales y también funcionan con expresiones regulares. Volveremos sobre esto en un concepto posterior.
A menudo necesitamos eliminar los espacios en blanco del principio y/o del final de un string.
Las funciones para esto son lstrip() (izquierda), rstrip() (derecha) y strip() (ambos lados).
julia> strip("\n\t useful_bit\n\n")
"useful_bit"
Estas funciones tienen nombres 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 "
A menudo resulta útil convertir entre números y su representación como string.
Convertir un número a string es sencillo, aunque una base distinta de la predeterminada, 10, necesita un parámetro adicional:
julia> string(42)
"42"
julia> string(42, base=16)
"2a"
julia> string(42, base=2)
"101010"
Analizar un string para obtener un valor numérico es algo más complicado y con más posibilidades de error. Es obligatorio especificar el tipo numérico de destino. Especificar la base es opcional y su valor predeterminado es 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
A veces resulta útil suprimir la interpolación y el escape habituales dentro de los strings.
Para ello, antepón raw a la comilla de apertura.
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"
Las expresiones regulares tienen su propia sintaxis, complicada, y Julia ofrece dos tipos de string muy útiles: r" " para la expresión regular y s" " para trabajar con fragmentos capturados.
Otros strings especiales son menos frecuentes en la programación al estilo de Exercism, aunque se usan mucho al crear bibliotecas.
v"x.y.z" gestionan la numeración mayor.menor.parche de las versiones de software.b" " omiten los límites de caracteres Unicode y operan a nivel de byte.