|
Eccoci ad un altro argomento tormentone dei linguaggo moderni. Le stringhe. Di fatto, per quanto nela mia esperienza, è una tipologia di dato che incontrerete con grande frequenza, seconda solo ai numeri, e la loro padronanza è un fattore di grande impotnaza. Dal punto di vista formale si tratta di sequenze indicizzate di caratteri UTF-8 (anche se come vedremo ci può essere qualche eccezione a questo insieme). Ci sono molti facili riferimenti sull'argomento liberamente accessibili pertanto non mi soffermerò su ulteriori dettagli riguardo a questa codifica. Per i nostri scopi non serve. In Crystal le stringhe sono delimitate da una coppia di doppi apici. Tutto ciò che cade all'interno fa parte della stringa e perde una sua eventuale diversa natura. Come ultimo, fondamentale punto, le stringhe in questo linguaggio sono immutabili. Cambiarne un elemento si può ma questo equivale a creare una nuova stringa. Vediamo ora qualche esempio, "ciao" "W la Juventus" "1234" "a + b * 123" "......" sono tutti esempi di stringa, anche se possono apparire altro. La codifica UTF-8 permette una ampia rappresentazione di caratteri il che estende le possibilità rappresentative anche a lingue diverse da quelle con alfabeto latino. Anche in questo linguaggio sono ammesse le solite stringhe standard che permettono di rappresentare caratteri particolari: "\"" # doppio apice "\\" # backslash "\e" # escape "\f" # form feed "\n" # a capo "\r" # ritorno del carrello "\t" # tabulazione orizzontale "\v" # tabulazione verticale e ovviamente all'interno delle stringhe stesse sono utilizzabili quei caratteri speciali che abbiamo visto nello studio del tipo char. La cosa in realtà può essere un po' più complessa ma possiamo fermarci qui.
Abbiamo altre notazioni possibili, analogamente a quanto visto per i caratteri: "\u0041" # == "A" oppure usando le parentesi graffe. "\u{41}" # == "A" Fin qui nulla di nuovo. Così come non è una novità il poter dividere una stringa su più righe tramite il carattere a capo o anche "fisicamente":
Le righe 2 e 3 compongono un'unica stringa, mentre alla riga 1 abbiamo l'uso di una sequenza speciale. Le possibilità rappresentative non finiscono qui:
Vi darà lo stesso output nonostante le differenze alla righe 2 e 4, in pratica gli spazi bianchi e i salti di riga vengono ignorati:
Questo sistema può venire utile ad esempio quando dovete scrivere righe particolarmente lunghe che volete, durante il processo di codifica, compattare a livello di visualizzazione in modo da poterle gestire meglio. Crystal supporta anche altre notazioni per definire le stringhe, insieme a quella classica dei doppia apici. La prima notazione prevede l'uso del carattere % a cui devono seguire dei delimitatori che possono essere parentesi tonde, quadre, graffe e le coppie <> e ||. A parte l'ultimo tutti gli altri possono essere innestati. Le stringhe che sono introdotte dal % possono gestire al loro interno il simbolo " s01 = %(ciao ("mondo")) puts s01 s02 = %(ciao ["mondo"]) puts s02 Al posto del simbolo % potete usare anche %q o %Q. La differenza è che %Q ha la stessa valenza del simbolo % mentre %q inibisce l'interpolazione e l'uso delle sequenze di escape. Potete dedicarvi ai vostri esperimenti. In questo ambito ci limiteremo ad usare stringhe senza particolarità concentrandoci maggiormente sui numerosi aspetti dell'oggetto stringa in sè. Va sottolineato che diversamente dal linguaggio Ruby, non sono supportate in Crystal le stringhe all'interno di una coppia di singoli apici, questo anche per non creare confusioni con la rappresentazione dei caratteri. Mi sembra una scelta corretta. Per inciso, sempre rimanendo nell'ambito della stampa delle stringhe, attraverso i range è possibile stampare solo una parte della stringa: s01 = "abcdefgh" puts s01[1..3] puts s01[0..5] output:
La costruzione di una stringa è abbastanza semplice, abbiamo già visto, una sequenza di caratteri all'interno di una coppia di doppi apici costituisce una stringa a tutti gli effetti, vedasi esempi a inizio paragrafo. E' interessante e molto comodo poter usare anche l'interpolazione per inserire non solo variabili ma anche vere e proprie espressioni: x01 = 1 x02 = 2 x03 = "somma = #{x01 + x02}" puts x03 che in output ci darà:
Tuttavia potete anche eliminare questa possibilità come segue, se volete che il risultato sia proprio la stringa così come è scritta; per fare questo basta premettere il carattere \ backslash prima del cancelletto: x04 = "somma = \#{x01 + x02}" e vi uscirà esattamente la stringa presente entro la coppia di doppi apici. Oppure, come visto, potete usare %q per questo scopo. Se volete creare una stringa vuota potrete anche usare new in alternativa a s01 = ""
Come vedete size vi indica la lunghezza, ovvero il numero di caratteri di una stringa. Il metodo empty (s01.empty usando la stringa dell'esempio) è un metodo invece che ritorna true se la stringa è vuota. All'interno di una stringa i caratteri sono, come avevamo detto ad inzio paragrafo, indicizzati, esattamente allo stesso modo visto per gli elementi di un array. Gli indici, anche qui, sono interi sequenziali che iniziano da 0. Quindi, da un punto di vista visuale, la stringa "abcdef" può essere rappresentata così:
dove la seconda riga rappresenta gli indici e la terza gli indici negativi, presenti anche qui come negli array. L'accesso ai singoli elementi di una stringa avviene attraverso il consueto operatore []. Attraverso il seguente esempio vediamo l'operatore al lavoro e impariamo un altro concetto:
che presenta questo output:
quindi:
Abbiamo visto come creare una stringa. Come possiamo aggiungere caratteri / stringhe a questa o comunque ad una stringa esistente? La questione non è banale, anche perchè in programmi di grossa portata l'impatto di un simile procedimento puà essere pesante. Un primo sistema per legare più stringhe è utilizzare un overload dell'operatore + che, oltre che ai numeri, possiamo applicare alle stringhe:
output:
Questo funziona ovviamente anche con una stringa vuota in partenza, ad esempio creata con String.new. E funziona anche per aggiungere un singolo carattere alla stringa: s01 = String.new s01 = s01 + 'a' puts s01 s01 = s01 + 'a' puts s01 Al momento in cui scrivo questo mi pare l'unico metodo semplice e contemporaneamente abbastanza efficiente per effettuare questa concatenazione. Interessante notare che potete usare, sia pure con altra finalità, l'operatore * che, come nel caso numerico, effettua una moltiplicazione: aa * 3 -> aaaaaa Per cercare una sottostringa all'interno di una stringa esiste index che restituisce la prima occorrenza della sottostringa cercata sia di una o più lettere oppure nil se nulla viene trovato.
che presenta questo output:
sono evidenziati gli indici iniziali delle 3 sottostringhe cercate mentre l'ultimo elemento, cercato alla riga 5, sarà nil in quanto la stringa "zz" non esiste in s01. Se invece volete sapere qual è il carattere ad un certo indice, avete char_at(indice) che restituisce proprio il carattere che si trova nella posizione dell'indice selezionato: puts "abcde".char_at(3) -> d puts "abcde"[3] -> d come è evidente questo metodo ha lo stesso esito dell'uso di [], è una questione di gusti. Tuttavia char_at ha una interessante variante. Se infatti usando [] selezionate un indice fuori range ne risulta un errore. Con char_at potete invece dire al metodo di esporre un carattere di allarme che vi dice che qualcsa è andato storto usando il formato char_at(indice){carattere}: puts "abcde".char_at(3){'?'} -> d puts "abcde".char_at(9){'?'} -> ? nel secondo caso, siccome 9 è oltre il range di indice validi per la stringa esce fuori il nostro carattere sentinella. Va detto, pur se ovvio, che se usate un carattere sentinella che è presente nella stringa questo può dar luogo ad ambiguità. Attenzione, quindi. Il metodo delete è molto potente ed ha vari utilizzi.
Come abbiamo visto sub sostituisce la prima occorrenza di una stringa o di un carattere con un'altra stringa. Ma se volessimo sostituire tutte le occorrenze? Allora possiamo usare gsub (global sub, in pratica).
con questo output:
Ora, l'elenco e le possibilità sono davvero tante e in questo paragrafo non intendo certo coprirle tutte. Gli esempi sono solo una traccia di quello che si può e come. la documentazione ufficiale vi indhcerà tutto quanto sia possibile fare, ovviamente. Per intanto vediamo alcuni altri metodi interessanti, sempre nell'ottica di garantire un uso immediato anche di questo strumento. includes? - restituisce true se la stringa contiene un certo carattere / stringa:
s01 = "abcde" index - restituisce l'indice della prima occorrenza di un carattere o di una stringa, se non è presente il risultato è nil.
s01 = "ciao come stai amico" insert - inserisce a partire dall'indice specificato un carattere o una stringa:
s01 = "aaaa" output:
reverse - rovescia la stringa puts "abc".reverse -> cba split è un interessante metodo e molto utile che permette di costruire un array in cui ogni elemento è una stringa ottenuta dalla suddivisione della stringa originaria eliminando gli spazi bianchi.
s01 = "prima parola seconda parola" output:
L'istruzione split può essere basata su altri separatori. Ne parleremo nella sezione degli esempi. Sempre nell'ambito della correlazione tra array e stringhe non possiamo non parlare dell'utile metodo chars che partendo da una stringa crea un array di caratteri in cui ogni singolo elemento è un carattere della stringa: s01 = "ciao"ar1 = s01.chars puts ar1 questo codice genera quindi un array di caratteri. strip di default elimina gli spazi iniziali e finali ma, diciamo a richiesta, anche altri caratteri specificandoli quando necessario:
puts " abcd ".strip -> abcd Esistono anche lstrip e rstrip che effettuano la stessa operazione di strip ma rispettivamente a sinistra e destra della stringa su cui lavorate. Se invece dovete eliminare il cosiddetto "a capo" potete usare stringa.chomp Abbiamo poi il solito insieme di metodi che manipolano maiuscole e miniscole in vari modi, vediamo con esempio:
Output:
La riga 1 rende tutta una stringa minuscola, la riga 2 pone in maiuscolo le inizali dopo uno spazio, la riga 3 rende maiuscola l'iniziale, la 4, fa il contrario della 1 e rende maiuscola tutta una stringa. Su caratteri che non appartengono alle lettere questi metodi non hanno alcun effetto e nemmeno vanno in errore.Non fanno nulla. Se vi chiedete quali problematiche esistano per copiare una stringa potete tirare un sospiro di sollievo. Le stringhe sono immutabili, come abbiamo detto all'inizio e quindi copiare una stringa in un'altra vuole dire crearne una nuova indipendente. Per creare un copia di una stringa potete usare il metodo dup o il semplice =, non cambia nulla e il risultato è lo stesso. L'esempio che segue illustra le due modalità ed anche il fatto che le modifiche sulla stringa origine non impattano sulle copie. Potete facilmente verificare da soli anche l'opposto.
e quindi il risultato sarà:
Ricorderete che dalle stringhe, non fosse che per il fatto che lo standard input viene trattato in forma, appunto, di stringa, si può passare ad altre tipologie di dati, in particolare numerici, Esiste un numero grande di metodi che compiono questa traslazione, alcuni li abbiamo già visti e sul sito ufficiale li trovate proprio tutti. to_i, to_u8, to_f64, to_i32, ecc... sono tutti lì e il loro uso e significato sono abbastanza evidenti. Come abbiamo evidenziato è ovvio che la stringa deve essere nel formato corretto perchè la conversione abbia successo. Interessanti a questo proposito sono i metodi di conversione vero tipi numerici che terminano con un punto interrogativo. Questi metodi restituiscono in output un tipo unione, ne parleremo in apposito paragrafo, aiutandoci ad evitare crash indesiderati. L'esempio seguente mostra la differenza nel caso di uso di un metodo "normale" e di uno diciamo "esteso" s01 = "a".to_i puts typeof(s01) Questo codice non può funzionare e dà anzi origine ad un erroraccio. Il motivo è, ovviamente, che la stringa "a" non può essere convertita in un numero. Se invece modifichiamo il nostro piccolo programma così: s01 = "a".to_i? puts typeof(s01) ci esce fuori: (Int32 | Nil) ovvero un tipo unione. Tenete presente questa possibilità. Concludiamo questo paragrafo... tornando all'inizio. Abbiamo detto che String.new crea una stringa vuota. Ruby permette di scrivere espressioni come: s01 = String.new("ciao") ma in Crystal questo è un errore. Error: expected argument #1 to 'String.new' to be Pointer(UInt8) or Slice(UInt8), not String Un possibile uso di new quindi è il seguente:
Qui l'output è:
ovvero una stringa costituita dai caratteri e da un "vuoto" che dipende dal fatto che abbiamo inserito un carattere che non è UTF-8 (come si può constatare con s02.valid_encoding? che restituisce false. Questo è un problema in quanto non ci sono avvisi di questa errata codifica della stringa. Il trattamento delle stringhe è veramente uno degli argomenti fondamentali in qualsiasi linguaggio e quanto abbiamo visto non è certamente completo, anzi. Ad esempio essendo così tanti i metodi di manipolazione è impossibile essere esaustivi e vi consiglio vivamente di andare sul sito ufficiale e provarli un po'. Troverete tante cose interessanti e utili e sarà un'esperienza formativa districarsi nei vari formati. Inoltre, riparleremo delle stringhe in congiunzione con l'importante capitolo delle funzioni. |