Come affrontare e risolvere un problema complesso.
Abbiamo visto come scrivere funzioni sempre più flessibili, ma in genere chi programma si trova a dover risolvere problemi più complessi, cioè costituiti da più parti messe in relazione tra di loro.
La prima attività da compiere quando si affronta un problema complesso è quella di analizzare il problema. Analizzare un problema significa:
scomporlo in problemi più semplici,
studiare le relazioni tra le parti,
analizzare ogni singola parte fino a ottenere parti semplici.
Applichiamo questo metodo a un problema abbastanza semplice: disegnare una casa stereotipata.
Lo strumento che usiamo per l’analisi è il “grafo ad albero”. Il grafo richiama un po’ la struttura di un albero rovesciato: la radice in alto e le foglie in basso. Nella radice viene messo il risultato finale, quello che vogliamo ottenere. Da questa partono alcuni rami che conducono a nodi che contengono le parti in cui il disegno può essere scomposto. A loro volta, queste parti vengono scomposte in altre parti fino ad arrivare a elementi abbastanza semplici da essere disegnati da una sola funzione. In ognuno di questi elementi deve essere indicata la posizione di partenza e di arrivo della tartaruga e un nome.
In questo grafo ad albero:
Le varie parti della casetta costituiscono i nodi.
Le frecce costituiscono i rami.
Il nodo da cui parte tutto l’albero è la radice.
I nodi da cui non parte alcun ramo sono le foglie.
La sua analisi è abbastanza semplice, si riduce ad un cespuglio formato da una radice, quattro rami e quattro foglie.
Per disegnare la casa bisogna riuscire a disegnare un triangolo, un quadrato, un rettangolo e un parall(elogramma).
Scritta l’intestazione e le due righe che importano le librerie necessarie, passiamo scrivere le quattro funzioni necessarie. Perché tutto si incastri correttamente bisogna anche stabilire le dimensioni delle varie parti in modo coerente. Se costruiamo un triangolo e un quadrato di lato 50 allora il rettangolo dovrà avere una dimensione di 50 e l’altra potrebbe essere di 80. Il parallelogramma dovrà avere i lati di ... e il primo angolo esterno di ... gradi.
Per quanto riguarda la quinta funzione, casa(), si potrebbe pensare di spostare la tartaruga nel punto comune a tutti quattro i moduli che compongono la casa, disegnarli e poi riportare la tartaruga dove era stata presa. La funzione potrebbe somigliare a:
def casa():
"""Disegna una casa."""
tina.forward(50)
tina.left(90)
tina.forward(50)
tina.left(30)
triangolo()
tina.left(60)
quadrato()
tina.left(90)
rettangolo()
tina.left(90)
parall()
tina.left(90)
tina.back(50)
tina.right(90)
tina.back(50)
Eseguendo questa funzione possiamo vedere che l’analisi presentava un errore. Nella realtà i processi non sono lineari spesso una fase di lavoro comporta una revisione delle fasi precedenti.
Ora ci poniamo il problema di scrivere le funzioni che disegnano un orribile ragnetto.
Per prima cosa analizziamo il problema che è più complesso del precedente. Possiamo suddividere il problema nei due sotto problemi: disegnare il corpo e la testa. Possiamo considerare come primitivi i disegni della circonferenza e dell’arco che disegna la testa quindi restano da disegnare le zampe e le antenne. Da notare però che le zampe destre sono diverse da quelle sinistre. Per complicarci un po’ la vita possiamo realizzare un ragno con una dimensione variabile.
A questo punto possiamo passare a scrivere il codice: un nuovo file, salvarlo con un nome adatto, poi:
la solita intestazione che dà le informazioni di base,
la lettura delle librerie,
lo spazio per le funzioni,
il programma principale,
Dovremmo aver scritto qualcosa che assomiglia a questo:
# <data>
# <nome>
# Un orribile ragnetto
# lettura delle librerie
from future import division
from pyturtle import TurtlePlane, Turtle
# Definizioni di funzioni
# Programma principale
foglio = TurtlePlane()
tina = Turtle()
ragno(50)
Abbiamo scritto il programma principale che produce il nostro disegno, ora lo proviamo: <F5>.
Effettivamente non è molto credibile che un programma siffatto possa disegnare il ragnetto che abbiamo in mente noi. Analizziamo il messaggio di errore ottenuto:
Traceback (most recent call last):
File .../06ragno.py, line 15, in <module>
ragno()
NameError: name 'ragno' is not defined
la prima riga ci dice che c’è un errore;
la seconda ci dice dove si trova l’errore;
la terza riporta la riga di programma incriminata;
la quarta ci dice perché Python non è in grado di eseguire il programma.
L’ultima riga del messaggio ci dice che Python non sa eseguire ragno(), bene partiamo da qui e diciamoglielo noi definendo la funzione che disegna tutto il ragno.
Il ragno è costituito dal corpo, da uno spostamento senza lasciare segno, dalla testa e da un altro spostamento, per rimettere la tartaruga al suo posto:
def ragno(dim):
"""Disegna un orribile ragnetto."""
corpo(dim)
tina.up()
tina.forward(dim)
tina.down()
testa(dim/2)
tina.up()
tina.back(dim)
tina.down()
<F5> per eseguire il programma, ma non otteniamo ancora nulla se non un altro messaggio che termina con la seguente riga:
NameError: global name 'corpo' is not defined
Python giustamente protesta che non gli abbiamo ancora detto come è fatto il corpo del ragno, non c’è problema, ci mettiamo subito al lavoro. L’addome del ragno può essere disegnato utilizzando il metodo ccircle(raggio, estensione) che permette di disegnare una circonferenza di raggio dato o un arco di circonferenza dati raggio e ampiezza dell’arco:
def corpo(dim):
"""Disegna il corpo del ragno."""
tina.ccircle(dim) # addome del ragno
tina.left(90)
zampes(dim)
tina.left(180)
zamped(dim)
tina.left(90)
<F5> per eseguire il programma... Questa volta qualcosa viene disegnato: il pancione del ragno, ma Python si interrompe ancora con un errore:
NameError: global name 'zampes' is not defined
Bene, questo messaggio ci dice che che è ora di scrivere la funzione zampes(dim). Questa funzione dovrà disegnare le quattro zampe sinistre che sono uguali tra di loro, solo ruotate di qualche grado:
def zampes(dim):
"""Le quattro zampe sinistre del ragno."""
tina.right(30)
for cont in range(4):
zampas(dim)
tina.left(20)
tina.right(20*4)
tina.left(30)
Facendoci guidare dagli errori che man mano Python rileva, procediamo a scrivere il resto del codice. In particolare la testa usa il metodo ccircle(raggio, estensione) per tracciare un arco di 210 gradi:
def testa(dim):
"""Disegna la testa del ragno."""
tina.right(105)
tina.ccircle(dim, 210)
tina.right(105)
tina.right(30)
antenna(dim, +60)
tina.left(60)
antenna(dim, -60)}
tina.right(30)}
Normalmente per risolvere problemi di matematica, nella scuola, si propone il metodo bottom up: si parte dai dati, si trova qualcosa di utile e via via ci si avvicina all’incognita. Questo metodo risulta di poco aiuto nella ricerca della soluzione.
Il metodo top down può fornire una guida preziosa nella soluzione dei problemi.
Prendiamo come esempio un problema di geometria solida:
Il volume di una piramide è e la base è un rombo il cui perimetro è e una diagonale è di . Calcola la misura dell’altezza.
Soluzione
Riassumendo
Prima di affrontare un problema complesso, bisogna suddividerlo in parti
facendone un’accurata analisi.
Possiamo risolvere un problema con il metodo “top-down” cioè partire dalla funzione più generale e realizzare man man mano le funzioni che sono richieste dal programma, controllando ogni volta che il programma, fino a quel punto funzioni correttamente.
Possiamo risolvere un problema con il metodo “bottom-up” cioè realizzare prima gli elementi più semplici, controllando che funzionino correttamente e metterli poi assieme per realizzare porzioni via via più complesse del problema