2. Cicli e funzioni Flashcards
La sintassi del ciclo for del linguaggio Swift
Come funziona un ciclo con il linguaggio Swift?
Nei linguaggi di programmazione in generale, quindi non solo per Swift, un ciclo rappresenta la capacità di ripetere un blocco di istruzioni. In più, un ciclo, può variare lo stato delle variabili del blocco in base a dei particolari criteri che vedremo man mano.
In pseudo codice, potrei razionalizzare un ciclo, in questa maniera:
ripeti per 10 volte {
print(“ciao xCoders”)
}
Cioè, esisterà una parola chiave o keyword che farà in modo di ripetere per n volte (nell’esempio 10) un blocco di codice (nell’esempio, stamperà per 10 volte la scritta “ciao xCoders”).
Se hai capito questo semplice esempio in pseudo codice, trasformarlo con il linguaggio Swift sarà, spero, altrettanto semplice.
La prima tipologia di ciclo, perché nè esistono diversi, si chiama ciclo for-in.
La sintassi è definita:
- Dalla parola chiave for.
- Seguita dal nome di una variabile chiamata, in gergo, iteratore.
- Dalla parola chiave in.
- E da un’intervallo di valori.
Più facile a farsi che a dirsi. Questa è la sintassi classica di un ciclo for in Swift:
for <iteratore> in <intervallo> {</intervallo></iteratore>
<blocco></blocco>
}
Puoi leggerla così: per ogni valore, contenuto nell’intervallo, esegui il blocco d’istruzioni.
Rappresentare un range o intervallo di valori
Facciamo una piccola digressione sui range di numeri in Swift.
Un range è la rappresentazione contratta di un array di Int. Viene definito il primo elemento x e l’ultimo elemento y, tra x e y vengono messi tre puntini (…) che stanno ad indicare che da x a y ci stanno tutti gli interi compresi tra x e y (x<=…<=y) come se fosse un array indicato solo con il valore della testa e della coda.
Quindi il range di numeri che comprende 1 e 10 si scrive così:
var range = 1…10
Il primo esempio ed il simbolo Underscore
Partiamo dall’esempio più semplice: stampare 10 print con 10 numeri diversi:
print(“1”)
print(“2”)
print(“3”)
print(“4”)
// ecc fino a 10
Quello che vuoi fare è creare un ciclo che, per ogni valore da 1 a 10, esegua l’istruzione print. Ogni volta, però, cambiando il numero da stampare.
Quindi, se volessi stampare i numeri da uno a dieci ti dovrai avvalere sicuramente del range 1…10.
Proviamo a scrivere un primo ciclo.
Gli elementi che abbiamo, per completare tutta la sintassi del ciclo, sono: la parola chiave for, la in ed il range. Ci manca la variabile o iteratore.
for <iteratore> in 1...10 {</iteratore>
// da definire
}
Per il momento, al posto del campo iteratore, puoi inserire il simbolo underscore _ che ti fa omettere questo passaggio:
let range = 1…10
for _ in range {
print(“ciao xCoders”)
}
Come si usa il campo iteratore?
Il campo variabile, o anche detto iteratore, assume, ad ogni ciclo, un valore del range da passare a rassegna. Puoi pensarlo come ad una variabile che ha lo scopo di assumere, ad ogni giro del ciclo, il valore del range successivo al precedente.
L’iteratore, la prima volta che viene eseguito il ciclo, assume il primo valore del range. Nel nostro caso, assumerebbe il valore 1 (dato che il range 1…10 parte dal valore 1).
Fin quando non vengono eseguite tutte le istruzioni del ciclo, quella variabile iteratore continuerà a mantenere il valore 1 e quindi, quel valore, potrà essere letto all’interno del blocco del ciclo per eseguire delle operazioni (per esempio la stampa). A fine ciclo, l’iteratore passa al valore successivo del range e continuerà così fin quando non termineranno tutti gli elementi del range.
Adesso, 2 cose dovrebbero essere chiare:
Indifferentemente dalla presenza o meno di una variabile, il ciclo, esegue tante volte il blocco d’istruzioni quanti sono gli elementi del range. Se il Range è di 100 elementi, il ciclo verrà eseguito per 100 volte.
La variabile di lettura o iteratore viene inserita solo quando si vuole utilizzare il valore del range, letto dal ciclo, all’interno del blocco d’istruzioni.
Esercizio 1. Stampare i numeri da 4 a 10.
Stampa i numeri che vanno da 4 a 10.
for iteratore in 4…10 {
print(iteratore)
}
Variabili interne ed esterne al ciclo
Ogni blocco d’istruzioni del ciclo devi immaginarlo come ad un qualcosa che viene eseguito ed infine resettato alla fine delle istruzioni presenti.
Per capire quello che voglio dire, prova a risolvere questo esercizio.
Esercizio 2. Crea una variabile con valore 10 che viene incrementata di una unità per quanti sono i valori presenti in un range. Per esempio, se il range è 1…4. La variabile, a fine ciclo, dovrà avere il valore 14 (perché nel range ci sono 4 numeri, quindi ad ogni numero del range ha sommato +1 al numero).
La tentazione del giovane sviluppatore è quella di scrivere qualcosa del genere:
for _ in 1…4 {
var numero = 10
numero += 1
print(numero)
}
Riesci a dirmi cos’è che fa questo ciclo? Se non ti arriva l’illuminazione divina, invece di sparare una risposta a caso, prendi il codice ed incollalo nel Playground.
Alla prima esecuzione, il ciclo si trova a leggere l’elemento 1 del range, entra dentro il blocco ed inizializza la variabile numero con il valore 10. Poi gli incrementa il valore di 1 (il simbolo += l’ho spiegato a metà di questa lezione) e stampa il contenuto della var.
Una volta finite tutte le istruzioni del blocco, al solito, controlla se ci sono o no altri elementi nel range da scorrere. Dato che si trova ancora alla posizione 1, ci sono altre 3 unità da scorrere. Il ciclo passa al valore numero 2.
Letto il numero 2 del range, rientra dentro il blocco e qui accade quello che non volevamo accadesse.
Ad ogni ciclo, la variabile numero viene inizializzata nuovamente al valore di 10. Quindi, la variabile numero dimentica tutto quello che è stato fatto nel ciclo precedente.
Come si risolve?
Si può adottare un piccolo stratagemma. Se inizializzi la variabile numero fuori dal ciclo, il suo valore non verrà mai sovrascritto a meno che non ordinato da te:
var numero = 10
for _ in 1…4 {
numero += 1
print(numero)
}
Dato che la var numero è stata inizializzata prima, ogni blocco la incrementerà sempre di un valore fin quando non avrà finito di passare a rassegna tutti i valori del range.
Array e ciclo for-in
Con il ciclo for in Swift sei in grado di leggere anche gli elementi di un array e di un dizionario in maniera intuitiva e veloce.
Riprendiamo velocemente cos’è un array. Sai, dalla lezione degli array e dizionari in Swift, che ogni elemento è conservato in una sorta di vagone identificato da un indice.
var treno = [
“linguaggio swift”,
“c”,
“objective-c”,
“Apple”
]
treno[0]
treno[1]
treno[2]
treno[3]
Con il ciclo for ed il linguaggio Swift sei in grado di passare a rassegna tutti i valori dell’array spostandoti da una posizione all’altra del treno proprio come hai visto fare con i range di numeri.
Nel caso degli array, se volessi scorrere i suoi elementi con il ciclo for, ti basterebbe inserire il suo nome dopo la parola chiave in:
for <iteratore> in <array> {</array></iteratore>
}
Se, nel range, la variabile iterativa leggeva uno ad uno i valori del range, nel vettore, l’iteratore legge i valori dell’array partendo dalla posizione 0.
Quindi, se all’interno del blocco d’istruzioni stampi il valore dell’iteratore, sulla console spunterà il valore di ogni elemento dell’array:
var treno = [
“linguaggio swift”,
“c”,
“objective-c”,
“Apple”
]
for vagone in treno {
print(vagone)
}
La storia è sempre la stessa:
Al primo giro la variabile vagone legge il contenuto dell’array alla posizione 0.
Entra dentro il blocco d’istruzioni e stampa “linguaggio swift“.
Finito il codice dentro il blocco, controlla se ci sono altri elementi da leggere.
Se ci sono torna alla riga for e la variabile vagone prende il contenuto dell’array in posizione 1. Entra nel blocco e stampa “c”.
Continua così fin quando non ha finito.
Dizionario e ciclo for
Considerata la presenza delle chiavi e dei valori, il ciclo for in Swift, si complica un pochino:
for <(chiave, valore)> in <dizionario> {</dizionario>
}
Ogni elemento di un dizionario del linguaggio Swift è composto sempre da due parti: una chiave ed un valore. Per questo motivo il ciclo deve poter leggere entrambe le parti in un unico colpo. Un po’ come se entrassi in un vagone composto da due cabine.
La variabile iteratrice viene definita da una coppia di variabili, racchiuse in parentesi tonde e divise da una virgola: (chiave, valore).
La variabile a sinistra della virgola leggerà sempre la chiave.
La variabile a destra della virgola leggerà sempre il valore associato alla chiave.
Applichiamolo ad un esempio.
Esercizio 3. Crea un dizionario la cui chiave rappresenta il nome di un dipendente di un’azienda ed il valore il suo salario. Crea un ciclo che stampa il nome ed il salario del dipendente.
let stipendiDipendenti = [“Giuseppe”: 1200.00, “Matteo”: 560.30, “Delia”: 2000.55, “Luca”: 755.99]
for (dipendente, stipendio) in stipendiDipendenti {
print(“Il dipendente (dipendente) guadagna (stipendio) “)
}
In alternativa puoi utilizzare una sola variabile iteratrice. La variabile ti permette di accedere sia alla chiave che al valore e per farlo devi inserire .0 per la chiave e .1 per il valore.
let stipendiDipendenti = [“Giuseppe”: 1200.00, “Matteo”: 560.30, “Delia”: 2000.55, “Luca”: 755.99]
for dipendente in stipendiDipendenti {
print(“chiave: (dipendente.0) valore: (dipendente.1) “)
}
While e Switch in Swift. Un altro modo per esprimere ciclicità e controllo
Con la lezione precedente, interamente incentrata sul ciclo for del linguaggio Swift, hai appreso che esiste un modo velocissimo per eseguire tante istruzioni con una manciata di righe di codice.
Come la programmazione ci insegna non esiste né un solo modo né una sola sintassi per risolvere un problema.
Ecco perché, in questa lezione, voglio estendere le tue conoscenze in materia di cicli e istruzioni di controllo, ti parlerò del ciclo while e ti introdurrò una nuova istruzione condizionale, lo switch.
Aspetta un secondo Peppe.
Perché dovrebbero esistere diversi modi per scrivere un ciclo o per fare un controllo del codice?
Il motivo per cui il linguaggio Swift, come anche altri linguaggi, utilizzano diverse parole chiave per esprimere lo stesso concetto, è paragonabile allo scegliere, o meno, una strada A piuttosto ché una strada B per raggiungere un punto.
Mi spiego meglio.
Durante lo sviluppo delle tue future applicazioni, ci saranno casi in cui, utilizzare un sistema o l’altro renderà più snello e leggibile il codice da te scritto.
Ovviamente esistono motivazioni di carattere tecnico che non terrò in considerazione in questo corso. Considerazioni che sono alla base della scelta del percorso A o B. La strada A, per esempio, è più veloce ma più usurante per la vettura (come una strada che taglia per una montagna), mentre la strada B è più lenta ma meno pesante per il guidatore.
Detto ciò, sei pronto a studiare l’istruzione While e Switch in Swift?
While e Repeat-While. Altri modi per esprimere la ciclicità con il linguaggio Swift
Oltre al ciclo for, il linguaggio Swift, mette a disposizione anche il ciclo while e repeat-while.
Il meccanismo è sempre lo stesso, eseguono in modo ricorsivo un numero determinato di istruzioni fin quando una certa condizione non è più verificata.
Il ciclo while in Swift lo chiamerai a condizione iniziale quando prima verifica che la condizione è vera e poi esegue il ciclo:
while <condizione> {</condizione>
<istruzioni></istruzioni>
}
var i = 0
while i < 5 { // finché i è < 5 esegui il ciclo
print(i += 1)
} // ad ogni ciclo ricontrollerà sempre la condizione
Mentre il ciclo repeat-while lo chiamerai a condizione finale quando la condizione di verifica si troverà alla fine. Quindi, prima esegue sempre un’istruzione e poi ne verifica la condizione:
repeat {
<istruzionii></istruzionii>
} while <condizione></condizione>
var i = 0
repeat {
i++
print(i)
} while i < 10
L’istruzione Switch. Gestire il flusso d’esecuzione in maniera intuitiva
Lo switch puoi considerarlo come un parente dell’istruzione if-else.
La sua caratteristica è quella di controllare lo stato di un valore e creare dei particolari blocchi di codice che si attivano solo se quel valore corrisponde al particolare caso da “acchiappare”.
Peppe, e che vor di?
Ti faccio vedere la sintassi e poi te la spiego:
switch variabileControllata {
case valore_1:
risponde al valore_1
case valore_2, valore_3:
risponde sia al valore_2 che al 3
default:
in caso contrario fai qualcos’altro
}
Un blocco di codice si attiva solo quando la variabileControllata è uguale ad uno dei valori dei case.
Mi spiego meglio, al solito, facendoti un esempio.
Esercizio 0. Implementa una istruzione switch che controlli se la lettera contenuta in una variabile è una vocale, consonante oppure nessuna dei due.
let unCarattere = “a” //cambia questo carattere per vedere altre possibilità
switch unCarattere {
case “a”, “e”, “i”, “o”, “u”:
print(“(unCarattere) è una vocale”)
case “b”, “c”, “d”, “f”, “g”, “h”, “l”, “m”, “n”, “p”, “q”, “r”, “s”, “t”, “v”, “z”:
print(“(unCarattere) è una consonante”)
default:
print(“(unCarattere) non è un né una vocale né una consonante”)
}
Ogni blocco d’istruzioni di un case comincia dopo i due punti.
Lo switch controlla il valore della variabile, entra dentro il codice, esegue il case dove il valore della variabile è acchiappato ed infine esce dallo switch.
A differenza dell’if-else, qui, il blocco di codice non ha bisogno di essere racchiuso da delle parentesi graffe. Basta inserirlo dopo i due punti che definiscono il case da considerare.
- Ogni case ha bisogno di almeno un’istruzione.
- L’intera istruzione switch deve sempre contenere un case di tipo default.
Stesso esempio con l’istruzione if-else
Ah si! Ti dicevo che è un parente dell’if else perché una logica di questo genere l’avresti potuta scrivere così:
var lettera = “a”
if (lettera == “a” || lettera == “e”) {
print(“(lettera) è una vocale”)
} else if (lettera == “b”) {
print()
} else {
print(“non è né una vocale né una consonante”)
}
Come puoi notare, utilizzando l’if-else avresti perso decisamente molto più tempo a scrivere il codice di controllo.
Quindi, già da questo semplice esempio, puoi cominciare a capire come le diverse istruzioni ti permetteranno di ottenere un codice migliore in base al tipo di problema.
Case che acchiappano intervalli di valori
Ci sono casi in cui ti piacerebbe controllare se un numero appartiene o meno ad un determinato intervallo.
Per esempio, proviamo a scrivere la logica per il controllo della vita di un giocatore.
Se la vita del giocatore, che come massimo è di 100 hp, si dovesse trovare nell’intervallo tra 50 e 0 allora diremo che è in fin di vita. Se invece si trovasse sopra i 50 e fino a 100, il giocatore sarà in buona salute. Altrimenti, se la vita dovesse essere 0, sarà morto.
Con lo switch del linguaggio Swift, per controllare se un valore è compreso tra due numeri, devi utilizzare il Range. I range li hai incontrati nella lezione del ciclo for-in. Per esempio i numeri che vanno da 0 a 50, si esprimono come range 0…50.
Con uno switch, questo esercizio può essere scritto così:
var hp_giocatore = 50 // cambia per vedere il risultato
switch hp_giocatore {
case 51…100:
print(“il giocatore è in ottima forma”)
case 1…50:
print(“il giocatore è in fin di vita”)
case 0:
print(“il giocatore è morto!!”)
default:
print(“valore non consentito max 100 e min 0 “)
}
Una cosa che salta subito all’occhio è la pulizia del codice rispetto all’equivalente con if-else.
Perché, tu hai già scritto lo stesso esercizio con l’if-else vero? Fallo immediatamente per rendertene conto tu stesso!
Tip. Nel caso in cui volessi escludere gli estremi, puoi utilizzare la forma del range contratto. Ovvero puoi scrivere un range in questo modo:
1..<50
Break, continue, fallthrough e return. I controllori del codice in swift
Il codice, come hai visto e appreso, viene eseguito a cascata ovvero parte dalla linea 1 e finisce alla linea x.
Esistono delle parole chiave che alterano il normale svolgimento a cascata,permettendo di saltare da un blocco di codice ad un altro.
Si chiamano break, continue, fallthrough e return. Le prime 3 te le spiegherò in questa lezione mentre l’ultima nella lezione sulle funzioni.
Continue
La parola continue comunica di andare avanti spostando l’esecuzione al prossimo ciclo. Un po’ come se dicesse: “Hei, io ho fatto quello che dovevo fare, puoi saltare avanti al prossimo ciclo senza dover finire l’attuale“.
Esercizio 3. Crea il codice che rimuove tutte le vocali da una stringa formando così una stringa semi criptata.
let stringaNonCriptata = “laMiaPassword”
var stringaCriptata = “”
for carattere in stringaNonCriptata.characters {
switch carattere {
case “a”, “e”, “i”, “o”, “u” :
continue // se trova una vocale va avanti al prossimo carattere (quindi al prossimo ciclo)
default: // se non è una vocale inserisce il carattere
//nella nuova stringa criptata
stringaCriptata.append(carattere)
}
}
print(stringaCriptata)
Senza la parola chiave continue non saresti riuscito a risolvere il problema dato che ogni case ha bisogno per forza di un’istruzione da eseguire.
Hai avuto problemi a capire il funzionamento di questa istruzione? Fammelo sapere con un commento! noi non mangiamo sviluppatori.
Break, fermati!
Se continue permetteva di “continuare”, scusa il gioco di parole, break arresta immediatamente l’esecuzione di un blocco di codice.
Break in un ciclo: Il break in un ciclo interrompe immediatamente l’esecuzione di tutto l’intero ciclo senza permettergli di andare ai successi.
L’esecuzione passa, dal ciclo, alla riga di codice immediatamente sotto la parentesi graffa } che chiude il ciclo.
Nell’esempio utilizzeremo il break per interrompere il salvataggio di una stringa appena incontra uno spazio vuoto.
let stringaSenzaSpaziVuoti = “AAAA BBBB” // questa stringa contiene uno spazio vuoto trs AAAA e BBBBB
var stringaFinale: String = “ “
for carattere in stringaSenzaSpaziVuoti.characters {
if (carattere == “ “) {
break
}
stringaFinale.append(carattere)
}
print(stringaFinale)
Il ciclo qui sopra avrebbe dovuto stampare tutta la stringa “AAAA BBBB” ma, avendo messo come condizione l’uscita dal ciclo quando trovato uno spazio vuoto, non ha la possibilità di farlo.
Continue o Break? Impara ad ottimizzare i tempi d’esecuzione
Saper mettere correttamente i break al posto giusto è quello che fa la differenza tra un buon programmatore e un programmatore.
Ogni riga impiega del tempo per essere eseguita.
Immagina che una stringa sia lunga migliaia e migliaia di elementi (ancora peggio se è un array o dizionario), il tuo compito è quello di scovare l’elemento intruso e di interrompere l’esecuzione al ritrovamento dello stesso.
Esercizio 4. Ferma l’esecuzione del ciclo al ritrovamento del carattere “1”, cerca di ottimizzare i tempi!
var stringa = “aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1aaaaaaaaaaaaaaaaaaaaaaaaa”
var i = 0
for valore in stringa.characters {
if (valore == “1”) {
print(“trovato intruso”)
continue // puoi anche non mettere niente
}
i += 1
print(“ciclo eseguito (i) volte”)
}
Con il continue il ciclo viene eseguito 82 volte, ovvero la stringa viene letta per intero anche se ha trovato l’elemento intruso. Con il Break il ciclo viene eseguito 57 volte. Questa è quello che intendo per ottimizzazione del codice.
var stringa = “aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1aaaaaaaaaaaaaaaaaaaaaaaaa”
var i = 0
for valore in stringa.characters {
if (valore == “1”) {
print(“trovato intruso”)
break
}
i += 1
print(“ciclo eseguito (i) volte”)
}
Anche se per adesso non sembra che ci sia differenza tra i due tipi di implementazione, quando passerai allo sviluppo delle applicazioni ti renderai conto dei tempi biblici che ci vogliono per cercare e recuperare informazioni da database e quant’altro.