La ricorsione è un argomento difficile da capire e non penso di poterle rendere pienamente giustizia qui. Invece, cercherò di concentrarmi sul particolare pezzo di codice che hai qui e cercherò di descrivere sia l'intuizione del perché la soluzione funziona sia la meccanica di come il codice calcola il suo risultato.
Il codice che hai fornito qui risolve il seguente problema: vuoi conoscere la somma di tutti gli interi da a a b, inclusi. Per il tuo esempio, vuoi la somma dei numeri da 2 a 5, inclusi, che è
2 + 3 + 4 + 5
Quando si cerca di risolvere un problema in modo ricorsivo, uno dei primi passi dovrebbe essere capire come scomporre il problema in un problema più piccolo con la stessa struttura. Supponiamo quindi di voler sommare i numeri da 2 a 5 inclusi. Un modo per semplificare ciò è notare che la somma di cui sopra può essere riscritta come
2 + (3 + 4 + 5)
Qui, (3 + 4 + 5) sembra essere la somma di tutti gli interi compresi tra 3 e 5, inclusi. In altre parole, se vuoi conoscere la somma di tutti gli interi compresi tra 2 e 5, inizia calcolando la somma di tutti gli interi compresi tra 3 e 5, quindi aggiungi 2.
Quindi come si calcola la somma di tutti gli interi compresi tra 3 e 5, inclusi? Ebbene, quella somma lo è
3 + 4 + 5
che può essere pensato invece come
3 + (4 + 5)
Qui, (4 + 5) è la somma di tutti gli interi compresi tra 4 e 5, inclusi. Quindi, se volessi calcolare la somma di tutti i numeri tra 3 e 5, inclusi, dovresti calcolare la somma di tutti i numeri interi tra 4 e 5, quindi aggiungere 3.
C'è uno schema qui! Se vuoi calcolare la somma degli interi tra a e b, inclusi, puoi fare quanto segue. Innanzitutto, calcola la somma degli interi compresi tra a + 1 e b, inclusi. Successivamente, aggiungi a a quel totale. Noterai che "calcolare la somma degli interi tra a + 1 eb, inclusi" sembra essere più o meno lo stesso tipo di problema che stiamo già cercando di risolvere, ma con parametri leggermente diversi. Piuttosto che calcolare da a a b, inclusi, stiamo calcolando da a + 1 a b, inclusi. Questo è il passaggio ricorsivo: per risolvere il problema più grande ("somma da a a b, inclusiva"), riduciamo il problema a una versione più piccola di se stesso ("somma da a + 1 a b, inclusiva").
Se dai un'occhiata al codice che hai sopra, noterai che c'è questo passaggio:
return a + sumInts(a + 1, b: b)
Questo codice è semplicemente una traduzione della logica di cui sopra: se vuoi sommare da a a b, inclusi, inizia sommando a + 1 in b, inclusi (questa è la chiamata ricorsiva a sumInt
s), quindi aggiungi a
.
Ovviamente, da solo questo approccio non funzionerà. Ad esempio, come calcolare la somma di tutti i numeri interi compresi tra 5 e 5 inclusi? Bene, usando la nostra logica corrente, dovresti calcolare la somma di tutti gli interi compresi tra 6 e 5, inclusi, quindi aggiungere 5. Quindi come si calcola la somma di tutti gli interi tra 6 e 5, inclusi? Bene, usando la nostra logica attuale, dovresti calcolare la somma di tutti i numeri interi tra 7 e 5, inclusi, quindi aggiungere 6. Noterai un problema qui - questo continua ad andare avanti!
Nel problem solving ricorsivo, ci deve essere un modo per smettere di semplificare il problema e invece andare a risolverlo direttamente. In genere, si trova un caso semplice in cui la risposta può essere determinata immediatamente, quindi si struttura la soluzione per risolvere direttamente casi semplici quando si presentano. Questo è in genere chiamato caso base o base ricorsiva .
Allora qual è il caso di base in questo particolare problema? Quando si sommano numeri interi da a a b, inclusi, se a sembra essere più grande di b, la risposta è 0 - non ci sono numeri nell'intervallo! Pertanto, struttureremo la nostra soluzione come segue:
- Se a> b, la risposta è 0.
- Altrimenti (a ≤ b), ottieni la risposta come segue:
- Calcola la somma degli interi compresi tra a + 1 e b.
- Aggiungi una per ottenere la risposta.
Ora confronta questo pseudocodice con il tuo codice effettivo:
func sumInts(a: Int, b: Int) -> Int {
if (a > b) {
return 0
} else {
return a + sumInts(a + 1, b: b)
}
}
Si noti che c'è quasi esattamente una mappa uno-a-uno tra la soluzione delineata in pseudocodice e questo codice effettivo. Il primo passo è il caso base: nel caso in cui chiedi la somma di un intervallo di numeri vuoto, ottieni 0. Altrimenti, calcola la somma tra a + 1 eb, quindi aggiungi a.
Finora ho fornito solo un'idea di alto livello dietro il codice. Ma avevi altre due ottime domande. Primo, perché non restituisce sempre 0, dato che la funzione dice di restituire 0 se a> b? Secondo, da dove viene effettivamente il 14? Diamo un'occhiata a questi a turno.
Proviamo un caso molto, molto semplice. Cosa succede se chiami sumInts(6, 5)
? In questo caso, tracciando il codice, vedrai che la funzione restituisce solo 0. Questa è la cosa giusta da fare, per - non ci sono numeri nell'intervallo. Ora prova qualcosa di più difficile. Cosa succede quando chiami sumInts(5, 5)
? Bene, ecco cosa succede:
- Tu chiami
sumInts(5, 5)
. else
Cadiamo nel ramo, che restituisce il valore di `a + sumInts (6, 5).
- Per
sumInts(5, 5)
determinare cosa sumInts(6, 5)
sia, dobbiamo mettere in pausa ciò che stiamo facendo e fare una chiamata a sumInts(6, 5)
.
sumInts(6, 5)
viene chiamato. Entra in if
filiale e ritorna 0
. Tuttavia, questa istanza di è sumInts
stata chiamata da sumInts(5, 5)
, quindi il valore restituito viene comunicato a sumInts(5, 5)
, non al chiamante di primo livello.
sumInts(5, 5)
ora può calcolare 5 + sumInts(6, 5)
per tornare indietro 5
. Quindi lo restituisce al chiamante di primo livello.
Nota come è stato formato il valore 5 qui. Abbiamo iniziato con una chiamata attiva a sumInts
. Ciò ha attivato un'altra chiamata ricorsiva e il valore restituito da quella chiamata ha comunicato le informazioni a sumInts(5, 5)
. La chiamata a sumInts(5, 5)
quindi a sua volta ha eseguito dei calcoli e ha restituito un valore al chiamante.
Se lo provi sumInts(4, 5)
, ecco cosa succederà:
sumInts(4, 5)
cerca di tornare 4 + sumInts(5, 5)
. Per farlo, chiama sumInts(5, 5)
.
sumInts(5, 5)
cerca di tornare 5 + sumInts(6, 5)
. Per farlo, chiama sumInts(6, 5)
.
sumInts(6, 5)
restituisce 0 a sumInts(5, 5).</li>
<li>
sumInts (5, 5) now has a value for
sumInts (6, 5) , namely 0. It then returns
5 + 0 = 5`.
sumInts(4, 5)
ora ha un valore per sumInts(5, 5)
, cioè 5. Quindi ritorna 4 + 5 = 9
.
In altre parole, il valore restituito è formato sommando i valori uno alla volta, ogni volta prendendo un valore restituito da una particolare chiamata ricorsiva a sumInts
e aggiungendo il valore corrente di a
. Quando la ricorsione tocca il fondo, la chiamata più profonda restituisce 0. Tuttavia, quel valore non esce immediatamente dalla catena di chiamate ricorsive; invece, restituisce semplicemente il valore alla chiamata ricorsiva uno strato sopra di esso. In questo modo, ogni chiamata ricorsiva aggiunge semplicemente un numero in più e lo restituisce più in alto nella catena, culminando con la somma complessiva. Come esercizio, prova a tracciarlosumInts(2, 5)
, che è ciò con cui volevi iniziare.
Spero che questo ti aiuti!