For loop in Swift
Uno dei control flow più importanti in computer science è sicuramente il for
loop. L'istruzione for
permette di navigare una collezione o range di elementi e, per ogni iterazione, eseguire un blocco di codice.
Il ciclo for
è l'istruzione che sta alla base di componenti grafici come List in SwiftUI o in metodi come filter
e contains
offerti dal type Array.
Capire come si crea un for
loop e come funzionano è importante per due motivi principali:
Alcune funzionalità dei tuoi futuri progetti potrebbero dipendere dal sapere iterare collezioni di elementi.
Per esempio, il gioco Tic Tac Toe (il Tris) richiede saper iterare una matrice oArray
diArray
. A fine di questo modulo vedremo come realizzarlo.Se stai cercando lavoro, molti dei test riguardano risolvere problemi di iterazione senza utilizzare le funzionalità del framework Foundation.
Quindi, indipendentemente dalle motivazioni, apri il Playground e vediamo insieme come creare un ciclo for
con il linguaggio Swift.
Come creare un ciclo for in Swift
Un loop per il linguaggio Swift può essere creato utilizzando le due keyword principali: for
ed in
:
for iterator in collection {
}
La keyword for
sta ad indicare che stiamo creando un loop mentre la keyword in
identifica la collezione o range che attraverseremo durante il ciclo for
.
Per iterator si intende la costante che cattura, per ogni iterazione, i valori della collezione. Puoi usare qualsiasi nome come iterator ma, in linea di massima, si utilizza un nome singolare.
Il blocco di codice all'interno del ciclo for
viene eseguito per ogni valore della collection
ed i valori vengono attraversati da sinistra verso destra.
let numbers = [9, -10, 30, -45]
for number in numbers {
print(number)
}
Nell'esempio, l'iterator chiamato number
cattura i valori della collezione numbers
e, per ogni ciclo, viene stampato il numero in console.
Facciamo un altro esempio ed immaginiamo d'avere Array
di nomi dove, per ognuno di loro, vogliamo sapere da quanti caratteri è composto. Allora potremo utilizzare il for in
così:
let names = ["Giuseppe", "Emily", "Luca", "Enzo"]
for name in names {
print("\(name) has \(name.count) characters")
}
Complichiamo leggermente il nostro ciclo for ed ipotizziamo di voler mostrare un messaggio d'errore se l'array contiene un nome vuoto. Nessuno ci vieta di poter aggiungere un'istruzione if all'interno dei ciclo for:
let names = ["Giuseppe", "Emily", "", "Luca", "Enzo"]
for name in names {
if name.isEmpty {
print("Error: there is an empty element")
} else {
print("\(name) is \(name.count) characters long")
}
}
Il componente ForEach di SwiftUI è l'equivalente di un ciclo for in.
Scope del ciclo for
Prima di passare a qualche esercizio, è importante capire che il codice definito all'interno del for
viene resettato ogni qual volta l'iterator passa da un valore al successivo.
Immaginiamo di voler sommare tutti i valori contenuti all'interno di un Array
, se definisco la var sum
all'interno del ciclo for
questa verrà distrutta e creata ad ogni iterazione, ottenendo un risultato inaspettato:
for number in [4, 6, 5] {
var sum = 0
sum = sum + number
print(sum) // WRONG! sum = 5
}
Ovvero sum
, venendo inizializzata con il valore 0
ad ogni iterazione, il ciclo finisce sommando sum + 5
(che è l'ultimo valore dell'array).
Per risolvere questo problema, la variabile sum
deve essere inizializzata fuori:
var sum = 0
for number in [4, 6, 5] {
sum = sum + number
}
print(sum)
Enumerated for-in loop
In certe circostanze potrebbe capitare di voler catturare, insieme ad ogni elemento, anche la posizione (aka index) in cui si trova.
In questi casi, viene in aiuto il metodo degli Array
enumerated()
che restituisce una collezione speciale chiamata EnumeratedSequence
dove ogni elemento viene trasformato in una coppia di valori: index e l'oggetto trasportato.
Per poter attraversare una EnumeratedSequence
dovrai utilizzare un Iterator con due valori (index, object)
:
var names = ["Giuseppe", "Emily", "Luca", "Enzo"]
print(names)
for (index, name) in names.enumerated() {
print("\(name) at index: \(index)")
}
// Giuseppe at index: 0
// Emily at index: 1
// Luca at index: 2
// Enzo at index: 3
Utilizzare sequenze enumerated
ti permetterà, per esempio, di modificare l'array senza dover cercare l'indice dell'elemento iterato.
var names = ["Giuseppe", "Emily", "", "Luca", "Enzo"]
for (index, name) in names.enumerated() {
if name.isEmpty {
print("Error: there is an empty element at index: \(index)")
names.remove(at: index)
} else {
print("\(name) is \(name.count) characters long")
}
}
print(names)
Nell'esempio sopra ho utilizzato index
per eliminare dall'array names
gli oggetti con stringhe vuote. Senza utilizzare enumerated
avrei dovuto cercare l'indice ed eventualmente accertarmi di star eliminando l'elemento corretto.
enumerated in SwiftUI
Nel caso in cui volessi utilizzare il metodo enumerated
all'interno di List
o ForEach
, dovrai convertire la EnumeratedSequence
in un nuovo Array
.
ForEach(Array(names.enumerated()), id: \.element) { index, element in
Text(element)
}
Nella lezione precedente, avremmo potuto utilizzare enumerated
per semplificare il codice delle swipeActions
:
ForEach(Array(todos.enumerated()), id: \.element) { index, todo in
Text(todo)
.swipeActions(edge: .leading) {
Button("Complete") {
todos.remove(at: index)
completedCount += 1
}
.tint(.green)
}
.swipeActions(edge: .trailing) {
Button("Delete", role: .destructive) {
todos.remove(at: index)
deletedCount += 1
}
}
}
Infatti, senza enumerated
avremmo dovuto recuperare l'index utilizzando il metodo firstIndex(of:)
.
Convertire
enumerated
inArray
è un'operazione obbligatoria perché siaList
cheForEach
possono interagire solamente con type che implementano il protocolloRandomAccessCollection
.
Esercizio 0. Trova il valore max di un Array
Il ciclo for
è probabilmente una delle istruzioni più complesse da comprendere, per questo motivo è importante esercitarsi fin dal primo istante si incontra questo nuovo strumento.
Il primo esercizio che devi provare a svolgere è: trova il numero più grande all'interno di un Array
di Int
.
var numbers = [5, -4, 55, 9, 100, 0, 22]
for number in numbers {
// max =
}
// print(max) - 100
Tips
Per poter risolvere questo problema ti serviranno due strumenti:
-
Una
var
in cui conservare il valore max. Potresti, per esempio, ipotizzare che il primo valore dell'array è il più grande ed eventualmente sostituirlo non appena il ciclofor
entra in funzione.Attenzione a dove definisci questa
var
, infatti aggiungerla all'interno del ciclofor
potrebbe avere un comportamento inaspettato.
Un
if
per poter confrontare il valore iterato dal ciclo con il valore considerato come max (ed eventualmente sostituirlo).
Al solito, scrivimi sotto nei commenti la tua soluzione o se hai avuto dei problemi.
Esercizio 1. Moltiplica gli elementi di due array
Immaginiamo d'avere due array points
e factors
con stessa dimensione (stesso numero di elementi):
Utilizza un ciclo for per moltiplicare i i valori allo stesso indice tra di loro.
Conserva i valori all'interno di un array
results
let points: [Double] = [5, 10, 2, 4]
let factors: [Double] = [2, 3, 4, 1]
var results: [Double] = []
for (index, point) in points.enumerated() {
// results
}
print(results) // [10, 30, 8, 4]
Tip: utilizza il metodo
enumerated()
per recuperare l'index. Utilizza l'index per leggere i valori dell'altro array.
Control flow: continue
e break
Nel caso in cui volessi saltare un ciclo di iterazione o uscire dal ciclo puoi utilizzare rispettivamente le istruzioni continue
e break
.
Per esempio, immaginiamo di voler effettuare manualmente una ricerca all'interno di un array senza utilizzare il metodo contains
. Potresti utilizzare break
per evitare di iterare tutti gli elementi nel caso in cui il ciclo incontrasse l'elemento cercato prima della fine della collezione:
let names = ["Alice", "David", "Eve", "Bob", "Charlie"]
let searchName = "David"
for name in names {
if name == searchName {
print("\(searchName) found!")
break
}
print("looking at: \(name)")
}
Senza il break
il ciclo continuerebbe anche dopo aver incontrato l'elemento che stavamo cercando inizialmente.
In casi di array con migliaia o centinaia di miglia di elementi, utilizzare il break
può migliorare drasticamente le performance delle tue app.
Dall'altro lato, l'istruzione continue
ti permette di saltare al ciclo successivo evitando di eseguire il codice all'interno del for
.
Come esempio, ipotizziamo di voler trasformare gli elementi di un array di Int
da negativi in positivi, potremmo utilizzare il continue
per skippare gli elementi che già sono positivi:
var numbers = [1, -2, -3, 4, 5, 6, -7, 8, 9, -10]
for (index, number) in numbers.enumerated() {
if number > 0 {
// the element is already positive,
// continue to the next cycle
continue
}
numbers[index] = -number
}
print(numbers)
Infatti, se togli l'istruzione continue
e l'if
puoi notare come tutti gli elementi vengono trasformati nel loro opposto.
Senza conoscere l'istruzione
continue
avresti dovuto innestare il codice all'interno di unif
oelse
rendendo il codice leggermente più complesso.
Esercizio 2. Conta le vocali in una stringa
Utilizzando un ciclo for
conta le vocali presenti all'interno di una stringa. Le stringhe sono speciali collezioni, di conseguenza possono essere iterate con un for in
.
let text = "Swift is awesome!"
let vowels = ["a", "e", "i", "o", "u"]
var vowelCount = 0
for char in text.lowercased() {
}
Rappresentare Range di valori
Il linguaggio Swift offre un tipo di dato chiamato range che permette di rappresentare intervalli di valori. Per esempio, i numeri da 5 a 10 sono un intervallo di numeri Int
e potranno essere rappresentati utilizzando il type Range
o ClosedRange
.
I Range
a differenza degli Array
vengono definiti esplicitando solamente l'estremo inferiore lowerBound
e l'estremo superiore upperBound
. Swift penserà a riempire i valori intermedi.
Una volta che hai identificato i due estremi, potrai creare:
ClosedRange
, ovvero un range che contiene entrambi gli estremi, utilizzando la sintassi implicitalowerBound...upperBound
.Range
o range aperto, ovvero che non contiene l'estremo superiore, utilizzando la sintassilowerBound..<upperBound
.
0...5 // ClosedRange
0..<5 // Range
let fromZeroToFive: ClosedRange<Int> = 0...5
let fromZeroToFour: Range<Int> = 0..<4
Puoi apprezzare la differenza utilizzando il ciclo for
per iterarne i valori. L'iterator catturerà ogni singolo valore presente all'interno dell'intervallo:
for n in 0...5 {
print("n", n)
}
print("......")
for i in 0..<5 {
print("i", i)
}
I range sono particolarmente utili, oltre che per eseguire cicli costanti, per catturare valori che rientrano all'interno del loro intervallo. Infatti, ipotizziamo un'app che permette la registrazione ad utenti di età compresa tra 18 e 99, potremo scrivere il nostro if
o switch
così:
let age = 30
if (18...99).contains(age) {
print("user can signup")
}
switch age {
case 18...99:
print("user can signup")
default: break
}
Esercizio 3. Piramide di numeri
Utilizzando un ClosedRange
ed un ciclo for stampa in console il seguente output:
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
for i in 1...5 {
var row = ""
print(row)
}
Tips
In questo esercizio dovrai annidare un nuovo ciclo for
all'interno del ciclo principale. In questo modo potrai, per ogni valore del ciclo, creare i numeri che ti serviranno per riempire la riga.
Invece di guardare al ciclo nella sua interezza, concentrati sui singoli step. Per esempio, l'iterazione numero 3, ovvero dove i
ha valore 3
, genererà la stringa 1 2 3
. Come posso creare i numeri da 1 ad i
?
Potresti utilizzare un ClosedRange
specificando come upperBound
il valore i
:
1...i
Di conseguenza se isoliamo questo step in un esercizio a se, potremo creare la stringa 1 2 3
scrivendo il seguente codice:
let i = 3
var s = ""
for n in 1...i {
s += "\(n) "
}
print(s) // 1 2 3
Se adesso porti questo concetto all'interno del ciclo principale, dovresti essere in grado di completare l'esercizio.
Conclusione
Anche se molto delle operazioni che richiederebbero eseguire un ciclo for sono già rappresentate da metodi come filter
, firstIndex(of:)
, contains
etc, a mio avviso è comunque importante capire come utilizzare questo strumento.
Infatti, oltre a capire meglio il dietro le quinte, ci permetterà di creare codice strettamente legato ai requirements delle nostre app e che non potrebbe altrimenti essere riprodotto utilizzando i metodi standard.
Per qualsiasi problema, al solito, scrivimi un commento qui sotto.
Buona programmazione!