In questa lezione impari a
- capire caso base e caso ricorsivo
- scrivere funzioni ricorsive su numeri, stringhe e liste
- riconoscere quando la ricorsione è lenta
- velocizzarla con il decoratore @lru_cache
Una funzione ricorsiva è una funzione che chiama se stessa. Serve per i problemi che si possono spezzare in una versione più piccola dello stesso problema.
Il fattoriale
Il fattoriale di un numero n (si scrive n!) è il prodotto di tutti i numeri da 1 a n: 5! = 5 × 4 × 3 × 2 × 1 = 120. Ma si può anche dire che 5! = 5 × 4!, e che 4! = 4 × 3!, e così via fino a 0! = 1.
def fattoriale(n):
if n == 0: # caso base: si ferma
return 1
return n * fattoriale(n - 1) # caso ricorsivo: un problema più piccolo
print(fattoriale(5)) # 120
Ogni funzione ricorsiva ha due parti:
- il caso base, che dà la risposta direttamente e ferma la ricorsione
- il caso ricorsivo, in cui la funzione richiama se stessa con un input più piccolo
Attenzione: senza caso base la funzione si richiama all'infinito e Python si ferma con
RecursionError.
Come procede il calcolo
fattoriale(3)
= 3 * fattoriale(2)
= 3 * 2 * fattoriale(1)
= 3 * 2 * 1 * fattoriale(0)
= 3 * 2 * 1 * 1
= 6
Ricorsione su stringhe e liste
Lo stesso schema funziona con qualunque cosa si possa accorciare:
def inverti(s):
if len(s) <= 1:
return s
return inverti(s[1:]) + s[0]
def somma_lista(lista):
if not lista: # lista vuota
return 0
return lista[0] + somma_lista(lista[1:])
print(inverti("python")) # nohtyp
print(somma_lista([4, 8, 15, 16])) # 43
Quando la ricorsione è lenta: Fibonacci
Nella successione di Fibonacci ogni numero è la somma dei due precedenti: 0, 1, 1, 2, 3, 5, 8, 13…
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(10)) # 55
È elegante ma lentissima: per calcolare fib(35) la funzione ricalcola gli stessi valori milioni di volte. Provalo e cronometra.
I decoratori e @lru_cache
Un decoratore è una funzione che modifica il comportamento di un'altra funzione senza toccarne il codice. Si scrive con @ sopra la definizione.
Python ne ha uno pronto, @lru_cache, che ricorda i risultati già calcolati: se la funzione viene richiamata con lo stesso argomento, restituisce subito il valore salvato.
from functools import lru_cache
@lru_cache
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(100)) # istantaneo
Un decoratore fatto da te
def annuncia(funzione):
def nuova(*args):
print(f"Sto chiamando {funzione.__name__}{args}")
return funzione(*args)
return nuova
@annuncia
def somma(a, b):
return a + b
print(somma(2, 3))
Esercizi
Esercizio 1 – Potenza. Scrivi una funzione ricorsiva potenza(base, esponente) senza usare **.
Mostra la soluzione
def potenza(base, esponente):
if esponente == 0:
return 1
return base * potenza(base, esponente - 1)
print(potenza(2, 10)) # 1024
Esercizio 2 – Somma delle cifre. Scrivi una funzione ricorsiva che restituisce la somma delle cifre di un numero: 1234 dà 10.
Mostra la soluzione
def somma_cifre(n):
if n < 10:
return n
return n % 10 + somma_cifre(n // 10)
print(somma_cifre(1234)) # 10
n % 10 è l'ultima cifra, n // 10 è il numero senza l'ultima cifra.
Esercizio 3 – Palindromo. Scrivi una funzione ricorsiva che dice se una parola si legge uguale nei due sensi, come "anna" o "radar".
Mostra la soluzione
def palindromo(s):
if len(s) <= 1:
return True
if s[0] != s[-1]:
return False
return palindromo(s[1:-1])
print(palindromo("radar")) # True
print(palindromo("python")) # False
Esercizio 4 – Ricerca binaria. Data una lista ordinata, scrivi una funzione ricorsiva che dice se contiene un valore, guardando l'elemento centrale e scartando ogni volta metà della lista.
Mostra la soluzione
def cerca(lista, valore):
if not lista:
return False
centro = len(lista) // 2
if lista[centro] == valore:
return True
if valore < lista[centro]:
return cerca(lista[:centro], valore)
return cerca(lista[centro + 1:], valore)
numeri = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(cerca(numeri, 23)) # True
print(cerca(numeri, 7)) # False
Con 1000 elementi bastano al massimo 10 confronti: è lo stesso metodo che usi per cercare una parola nel vocabolario.