St

Stringhe in Julia

7 esercizi

Informazioni su Stringhe

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 Julia, tutte le stringhe sono Unicode.
  • Le stringhe che sono anche ASCII (lettere inglesi dalla A alla Z in maiuscolo e minuscolo, cifre e alcuni caratteri di punteggiatura) sono facili da usare.
  • Con la maggior parte degli altri sistemi di scrittura del mondo è possibile lavorare, ma richiede più attenzione.

Stringhe letterali

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"

Caratteri di escape

Come la maggior parte dei linguaggi a partire dal C, Julia usa una barra rovesciata \ per l'escape:

  1. Per inserire caratteri non stampabili, come il ritorno a capo \n dell'esempio precedente.
  2. Per proteggere caratteri che altrimenti avrebbero un significato speciale, come \$ (vedi sotto).

Iterazione

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.

Indicizzazione

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.

Costruire stringhe

Concatenazione

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.

Ripetizione

Semplice:

julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"

Interpolazione

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:

  • Avvolgere il calcolo in una funzione round().
  • Al di fuori del test runner di Exercism, il pacchetto PyFormattedStrings emula le f-string di Python.
  • La macro @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"

Funzioni per le stringhe

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.

Lunghezza

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

Suddivisione

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)

Trovare e sostituire

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.

Spazi bianchi

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"

Cambiare maiuscole e minuscole

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 "

Parsing

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

Stringhe speciali

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.

  • Le stringhe di versione semantica v"x.y.z" gestiscono la numerazione major.minor.patch per le versioni del software.
  • I letterali di array di byte b" " aggirano i confini tra caratteri Unicode e operano a livello di byte.
Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Stringhe

La pratica è bloccata

Sblocca 2 altri esercizi per esercitarti su Stringhe