Una String in Julia è una sequenza ordinata di caratteri. Come sottolinea il manuale: «Naturalmente, il vero problema sorge quando ci si chiede che cos'è un carattere».
Gran parte di questa complessità sarà trattata più in dettaglio nel concetto Chars, ma in breve:
In generale, le stringhe sono racchiuse tra virgolette doppie " ".
Le virgolette singole ' ' si usano solo per i Chars.
È anche possibile usare virgolette doppie triple """ """, che consentono di usare " all'interno della stringa senza dover fare l'escape.
Cosa ancora più importante, l'indentazione in eccesso indesiderata viene rimossa dalle stringhe su più righe.
julia> """
Multiline
String
"""
"Multiline\nString\n"
Come la maggior parte dei linguaggi a partire dal C, Julia usa una barra rovesciata \ per l'escape:
\n dell'esempio precedente.\$ (vedi sotto).Una stringa è una collezione iterabile, quindi un ciclo for ... in funziona senza bisogno di convertirla in un vettore di caratteri.
julia> s = "Julia"
"Julia"
# admittedly, this is a silly example
julia> for c in s; print(c, "; "); end
J; u; l; i; a;
Iterare in questo modo ha il vantaggio che Julia gestirà correttamente i confini tra i caratteri, anche per le stringhe non ASCII. L'importanza di questo punto diventerà più chiara nella prossima sezione.
In linea di principio, le stringhe si possono indicizzare esattamente come i vettori:
julia> s[1], s[5]
('J', 'a')
Funziona chiaramente per una stringa ASCII come «Julia», ma così si ignorano la maggior parte delle lingue del mondo.
Prendiamo uno dei caratteri del Beowulf, scritto in inglese antico circa 15 secoli fa.
«Hrōðgār» è chiaramente composto da 7 caratteri, ma non tutti sono 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)
Indicizzare una stringa in questo modo funziona all'inizio, ma a un certo punto si rompe:
julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)
julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'
Il problema è che indicizzare in questo modo conta i byte, che per le stringhe non ASCII non hanno una relazione 1:1 con i caratteri.
Un carattere Unicode/UTF-8 può richiedere fino a 4 byte per essere rappresentato, e il concetto Chars approfondirà questo aspetto.
Molti linguaggi usano + come operatore di concatenazione di stringhe, ma non Julia.
Almeno il messaggio di errore è utile.
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 funzione string() concatena un numero arbitrario di stringhe.
julia> string("lots ", "of ", "bits")
"lots of bits"
La funzione join() fa lo stesso e accetta anche un vettore di stringhe, concatenandole con un separatore specificato (il valore predefinito è "").
julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"
Nota che le stringhe sono immutabili e la concatenazione implica una copia. Quindi, concatenazioni ripetute all'interno di un ciclo sono inefficienti: è meglio raccogliere i frammenti in un vettore e unirli tutti alla fine.
Semplice:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
Molti linguaggi hanno un modo per inserire valori calcolati nelle stringhe, anche se c'è un disaccordo straordinario sulla sintassi migliore da usare.
In Julia, i valori interpolati sono preceduti da $.
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"
Le parentesi sono necessarie quando ometterle renderebbe il codice ambiguo, ma sono facoltative per un singolo identificatore seguito da uno spazio.
Per includere un $ nella stringa, fai l'escape con \$.
Nota il limite per cui l'interpolazione al momento non consente la formattazione, come il numero di cifre decimali da mostrare.
Tra le soluzioni alternative:
round().@sprintf() è descritta di seguito.@sprintf()Per un controllo più preciso sull'assemblaggio delle stringhe, Julia riprende la funzione sprintf dal C (e da diversi linguaggi successivi: Wikipedia ne elenca circa 30).
In Julia, è implementata come una macro all'interno del modulo Printf, da cui il prefisso @.
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"
Per semplicità, questa sezione si concentrerà sulle funzioni che restituiscono una nuova stringa e lasciano invariato l'input.
Molte hanno un equivalente, indicato con il suffisso !, che modifica l'input sul posto.
Quanto è lunga una stringa? A volte dipende da come la si conta.
Il caso semplice è quello delle stringhe ASCII, in cui ogni carattere occupa 1 byte.
julia> string_asc = "Julia"
"Julia"
julia> length(string_asc)
5
julia> sizeof(string_asc)
5
La funzione length() restituisce il numero di caratteri, mentre la funzione sizeof() il numero di byte.
Questa distinzione diventa importante con altri insiemi di caratteri:
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> sizeof(king)
10
Un'operazione comune in programmazione consiste nel prendere una stringa lunga e suddividerla in pezzi, in base a una stringa separatore di uno o più caratteri.
Nel caso più generale, usiamo la funzione split().
Il separatore predefinito è uno spazio bianco qualsiasi, ma se ne possono specificare altri.
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"
Questi esempi suddividono una stringa in stringhe più piccole. Suddividere una stringa in caratteri è un'operazione diversa.
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)
Una stringa contiene una sottostringa specificata?
Stranamente, ci sono due funzioni per verificarlo: occursin() e contains() differiscono solo per l'ordine dei loro argomenti.
julia> needle = "Julia"
"Julia"
julia> haystack = "Python, Julia and R"
"Python, Julia and R"
julia> occursin(needle, haystack)
true
julia> contains(haystack, needle)
true
Per essere più precisi sulla posizione:
julia> startswith(haystack, "Python")
true
julia> endswith(haystack, "Python")
false
Per ottenere gli indici inizio:fine di una sottostringa, abbiamo diverse funzioni:
julia> findfirst(needle, haystack)
9:13
Per sostituire delle sottostringhe, elenca le modifiche usando la sintassi x => y.
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
A partire da Julia 1.7, la funzione replace può accettare un numero arbitrario di modifiche, con la garanzia che nessun carattere venga cambiato più di una volta durante la sostituzione.
La sintassi x => y si chiama pair, un tipo importante in Julia, trattato più in dettaglio nel concetto Dicts and Pairs.
Nota: per semplicità, tutti gli esempi precedenti usano stringhe letterali. Molte delle funzioni sono più generali e funzionano anche con le espressioni regolari. Torneremo su questo punto in un concetto successivo.
Spesso dobbiamo rimuovere gli spazi bianchi iniziali e/o finali da una stringa.
Le funzioni per farlo sono lstrip() (sinistra), rstrip() (destra), strip() (entrambi).
julia> strip("\n\t useful_bit\n\n")
"useful_bit"
Queste funzioni hanno nomi piuttosto autoesplicativi:
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 "
Spesso è utile convertire numeri e la loro rappresentazione sotto forma di stringa.
La conversione da numero a stringa è semplice, anche se una base diversa da quella predefinita (10) richiede un parametro in più:
julia> string(42)
"42"
julia> string(42, base=16)
"2a"
julia> string(42, base=2)
"101010"
Convertire una stringa per ottenere un valore numerico è un po' più complicato e comporta maggiori possibilità di errore. Specificare il tipo numerico di destinazione è obbligatorio. Specificare la base è facoltativo e il valore predefinito è 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 volte è utile sopprimere l'interpolazione e l'escape usuali all'interno delle stringhe.
Per farlo, anteponi raw alle virgolette di 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"
Le espressioni regolari hanno una sintassi complicata a sé stante, e Julia fornisce due utili tipi di stringa: r" " per le Regex e s" " per lavorare con i frammenti catturati.
Varie altre stringhe speciali sono meno comuni nella programmazione in stile Exercism, anche se ampiamente usate nella creazione di librerie.
v"x.y.z" gestiscono la numerazione major.minor.patch per le versioni del software.b" " aggirano i confini tra caratteri Unicode e operano a livello di byte.