15: WORKSHOP

METTIAMOCI ALL'OPERA

A questo punto abbiamo già acquisito le principali conoscenze sul linguaggio, e penso sia opportuno fermarci un attimo per consolidare quanto abbiamo appreso. In questa lunga lezione vedremo qualche applicazione delle funzionalità base di Python ed introdurremo qui e là qualche nuova caratteristica. Potete passare alle prossime lezioni anche senza svolgere subito tutti gli esempi che vi propongo qui; talvolta, però, faremo riferimento ad essi più avanti per modificarli introducendo nuove funzionalità.

UN MAZZO DI CARTE

Nell'informatica avremo spesso il problema di rappresentare una situazione della vita reale mediante un programma. Questo vuol dire che dobbiamo scegliere delle opportune strutture di dati, che contengano le informazioni che ci serve conoscere, e le opportune funzioni che operino su questi dati per ottenere i risultati che vogliamo. Un programma di una certa complessità necessita quindi di una fase di progettazione, che consiste essenzialmente nell'analizzare le informazioni che sarà necessario conoscere ed il modo in cui il programma deve manipolare queste informazioni, traducendo poi tutto ciò in elementi del linguaggio (variabili e funzioni).

Vediamo un semplice esempio: vogliamo rappresentare un mazzo di carte, che ci potrà poi servire per simulare dei giochi. Analizziamo per prima cosa le informazioni che ci serve conoscere:

Vediamo poi le operazioni che possiamo compiere sul mazzo:

Ecco una soluzione per il nostro problema:

NUOVO PROGRAMMA: mazzo_carte.py
ESERCIZIO 15.1 Creare il mazzo ed estrarre una carta
  1. Importate il modulo random ed inizializzate il generatore di numeri casuali (vedi qui)
  2. Copiate nel programma queste liste:
    semi = ["denari", "coppe", "spade", "bastoni"]
    valori = ["asso", "due", "tre", "quattro", "cinque", "sei", "sette", "fante", "cavallo", "re"]
  3. Create la list carte (usando la funzione range() e poi convertendo il range in list)
  4. Mescolate il mazzo, chiamando la funzione shuffle() con la lista come argomento: una volta chiamata i numeri saranno ordinati casualmente
  5. Per estrarre una carta scrivete c = carte.pop() (estrae l'ultima carta del mazzo e la assegna alla variabile c)
  6. Assegnate alla variabile s la prima cifra di c (il seme, da 0 a 3) ed alla variabile val la seconda (il valore, da 0 a 9: va bene così perchè dobbiamo usarlo come indice in una lista)
  7. A questo punto in semi[s] c'è il seme della nostra carta ed in valori[val] c'è il valore: stampate
    asso di coppe (oppure tre di spade ecc.)
SOLUZIONI

Vogliamo adesso estrarre più carte di seguito, come si fa in un gioco di carte. Il metodo più semplice è quello di inserire i passi 5 - 6 - 7 del programma in un ciclo for i in range(40): che estrarrà di seguito tutte le carte dal mazzo (provate).

Vogliamo però essere un po' più raffinati, e lasciare che sia l'utente ad estrarre la carta premendo Invio: stamperemo un messaggio del tipo "Premi q per uscire, Invio per estrarre una carta" e poi gli mostreremo la carta uscita. Tra l'altro, dato che l'utente non sa quante carte rimangono nel mazzo, dovremo gestire anche la possibilità che esso si svuoti: in questo caso (come si fa in molti giochi) rimescoleremo le carte e ricominceremo da capo. Modificate il programma così:

ESERCIZIO 15.2 Estrazioni ripetute
  1. I primi due passi sono come nel programma precedente
  2. Definite la list carte inizialmente vuota (la "riempiremo" nel ciclo principale, ogni volta che dobbiamo rimescolare il mazzo)
  3. A questo punto entrate in un ciclo while senza fine (guardate qui)
  4. Con una input() stampate il messaggio per l'utente e prendete in input la stringa risp
  5. Se risp è uguale a "q" uscite immediatamente dal while (ripassate la Lezione 9), altrimenti ...
  6. Prima di estrarre una carta dovete controllare se il mazzo è vuoto (basta controllare se la lunghezza della lista è 0). In questo caso inserite i passi 3 - 4 del programma precedente. Avvertite l'utente stampando "Attendi: sto mescolando il mazzo ..."
  7. Ora si può continuare con i passi 5 - 7 del programma precedente, (sempre all'interno del ciclo while)
  8. Alla fine, fuori dal while, saluta l'utente per confermare che il programma è terminato
Ecco un esempio di I/O:
Premi q per uscire, Invio per estrarre una carta
Attendi: sto mescolando il mazzo...
asso di denari
Premi q per uscire, Invio per estrarre una carta
fante di spade
Premi q per uscire, Invio per estrarre una carta 
fante di coppe
Premi q per uscire, Invio per estrarre una carta  q
Fine del programma
SOLUZIONI

Infine, dopo questo esercizio, potete utilizzare il mazzo di carte nel gioco banco.py che abbiamo già programmato qui: dovete modificare qualcosa sia nel programma originale sia in questo appena scritto, ve lo lascio come esercizio.

STRINGHE E CICLI "ARTISTICI"

Quando abbiamo introdotto le stringhe abbiamo detto che esistono tre sintassi diverse per definire una stringa: "mamma", 'mamma', """mamma""". Mentre le prime due sono equivalenti, la terza permette di andare a capo nella definizione della stringa, anche lasciando eventuali spazi bianchi iniziali nelle righe successive. In IDLE potete spezzare l'istruzione in più righe: dopo aver scritto la prima riga premete Invio, e vedrete che IDLE non vi risponderà ma vi manderà semplicemente a capo; la risposta arriverà solo quando premerete Invio dopo aver chiuso le tre virgolette.


>>> print ("""1
 2
  3
   4""")

1
 2
  3
   4

In effetti non è proprio facile interpretare questa (singola) istruzione. Nella prima riga ci sono la funzione print(), la parentesi e le triple virgolette aperte; c'è anche il primo carattere della stringa da stampare: "1". Ora la stringa continua nelle righe sottostanti andando a capo e finisce nella quarta riga con le triple virgolette e la parentesi chiusa (le altre quattro righe sono la risposta di IDLE); notate che gli spazi a sinistra nelle righe successive fanno parte della stringa e vengono stampati. Ecco un altro esempio:


>>> print("""
CCCCC  II  AAAAAA  OOOOOO
CC     II  AA  AA  OO  OO
CC     II  AA  AA  OO  OO
CC     II  AAAAAA  OO  OO
CCCCC  II  AA  AA  OOOOOO""")


CCCCC  II  AAAAAA  OOOOOO
CC     II  AA  AA  OO  OO
CC     II  AA  AA  OO  OO
CC     II  AAAAAA  OO  OO
CCCCC  II  AA  AA  OOOOOO

qui sono andato a capo subito dopo le """: questo vuol dire che la stringa inizia con un a capo (notate la riga bianca tra l'istruzione e la risposta). Ricordo due cose:

ESERCIZIO 15.3: Scrivi il seguente output:

PPPPPP   AAAAAA   CCCCCC   MM   MM   AAAAAA   NN   NN
PP  PP   AA  AA   CC       MMM MMM   AA  AA   NNN  NN
PPPPPP   AA  AA   CC       MMMMMMM   AA  AA   NNNN NN
PP       AAAAAA   CC       MM M MM   AAAAAA   NN NNNN
PP       AA  AA   CCCCCC   MM   MM   AA  AA   NN   NN

ESERCIZIO 15.4: Scrivi le tue iniziali

Anche i cicli for possono essere utilizzati per ottenere dei disegni.

NUOVO PROGRAMMA: cicli_artistici.py

Proviamo per prima cosa questo semplice programmino che usa la moltiplicazione di una stringa per un numero (di seguito è mostrato il suo output)


for i in range(1, 11):    # i da 1 a 10
    print(i *  "*")

*
**
***
****
*****
******
*******
********
*********
**********

Vogliamo ora ottenere questo output:


1
22
333
4444
55555
666666
7777777
88888888
999999999

Saremmo forse tentati di scrivere questo:


for i in range(1, 10):    # i da 1 a 9
    print(i * i)

ma se proviamo otterremo una serie di numeri, perchè nella seconda riga Python sta moltiplicando tra loro due numeri. Ci serve la funzione str() che è un po' l'inversa delle int() e float(), cioè trasforma, ad esempio, il numero 10 nella stringa "10"


for i in range(1, 10):    # 1 da 1 a 9
    print(i * str(i))
ESERCIZIO 15.5 Come potreste fare ad ottenere anche una riga con 10 zeri? Scrivere semplicemente for i in range(1, 11) non va bene perchè quando i diventa 10 ... Dovete scrivere solo l'ultima cifra del numero: è lo stesso problema che abbiamo incontrato nel mazzo di carte.
SOLUZIONI

Infine una tecnica un po' più raffinata è quella di scrivere due cicli for uno dentro l'altro (annidati): nel ciclo esterno (con il contatore i) definiamo una stringa s vuota, dopodichè usiamo un altro ciclo interno (con j) per aggiungere uno dopo l'altro i caratteri alla stringa. Usciti dal ciclo interno stamperemo la stringa (sareste forse tentati di stampare i caratteri direttamente nel ciclo interno, ma dovete ricordare che la print() va a capo ogni volta che viene chiamata, quindi possiamo stampare solo una riga per volta). Cercate di capire (eventualmente con il debugger) come fa questo programma ad ottenere l'output:


for i in range(1, 11):
    s = ""
    for j in range(1, 11):
        if j == i or j == 11 - i: 
            s += "+"
        else:
            s += "."
    print(s)

+........+
.+......+.
..+....+..
...+..+...
....++....
....++....
...+..+...
..+....+..
.+......+.
+........+
ESERCIZIO 15.6: Provate voi ad ottenere questi output (per semplicità li ho raggruppati su due righe, ogni figura è un esercizio separato). Tenete presente che ci sono molti modi diversi di ottenere lo stesso risultato (comunque si possono ottenere tutte le figure facilmente usando un solo ciclo), è importante però che il numero di caratteri stampati corrisponda esattamente al disegno (se vi viene una fila o un carattere in più o in meno controllate bene gli indici dei vostri range())

**********        A             OOOOOOOOOO              X X X X X
 *********         A             O        O              X X X X X
  ********          A             O        O            X X X X X
   *******           A             O        O            X X X X X
    ******            A             O        O          X X X X X
     *****             A             O        O          X X X X X
      ****              A             O        O        X X X X X
       ***               A             O        O        X X X X X
        **                A             O        O      X X X X X
         *                 A             OOOOOOOOOO      X X X X X


    MM                D          1110000
   MMMM              DDD         0222000
  MMMMMM            DDDDD        0033300
 MMMMMMMM          DDDDDDD       0004440
MMMMMMMMMM        DDDDDDDDD      0000555
SOLUZIONI

LA RADICE QUADRATA

L'introduzione del computer nel calcolo matematico ha reso estremamente semplici molti procedimenti che una volta richiedevano pesantissimi calcoli, lunghi e complicati anche con una semplice calcolatrice. In particolare un algoritmo iterativo è un procedimento che cerca di raggiungere un determintato risultato avvicinandosi sempre di più ad esso con varie prove: si parte da un valore di prova iniziale, si valuta l'errore che si è commesso e si cerca un nuovo valore di prova "più vicino" al risultato cercato. Ripetendo il procedimento ci si avvicina sempre più finchè si arriva alla precisione voluta.

Un semplice esempio è il cosiddetto "algoritmo babilonese" per il calcolo della radice quadrata (per una trattazione approfondita vedi Wikipedia). Supponiamo di dover trovare la radice quadrata di un numero n: questo equivale ad "indovinare" il lato di un quadrato che ha area n. Proviamo prima con un rettangolo di base b scelta a caso (ad esempio b = 1): facendo la divisione h = n / b troviamo l'altezza h del rettangolo. Ora, se b == h abbiamo trovato la radice (il rettangolo è un quadrato), se b > h abbiamo scelto b troppo grande e se b < h lo abbiamo scelto troppo piccolo: quindi la radice vera è un numero compreso tra b ed h. Allora basta prendere come nuova base b = (b + h) / 2 (la media tra b e h): questo numero è senz'altro più vicino alla radice che cerchiamo. Iterando questo procedimento otteniamo una successione di numeri b1, b2, b3, ... che si avvicinano sempre più alla nostra radice quadrata (in matematica si parla di successione convergente).

In questo tipo di algoritmi si usa di solito un ciclo while, perchè non si sa a priori quante volte si debba ripetere il procedimento. Per capire come dovremo impostare la condizione di controllo è necessario considerare che nel calcolo scientifico non si testa mai l'eguaglianza tra due numeri float (cioè con la virgola) con l'operatore ==, in quanto il computer esegue sempre calcoli approssimati (seppure con un numero molto grande di cifre decimali): questi calcoli possono portare ad errori di arrotondamento e quindi i numeri potrebbero risultare diversi anche se teoricamente dovrebbero essere uguali (o viceversa). Quindi due numeri float a e b sono considerati "uguali" se la loro distanza abs(a − b) (il valore assoluto di a − b) è molto piccola (tradizionalmente in matematica si usa la lettera greca ε (epsilon) per indicare un numero positivo molto piccolo).

Scriviamo quindi un programma che trova la radice quadrata di un numero; in esso inseriremo anche la stampa di tutti i risultati intermedi, per "vedere" meglio il nostro algoritmo all'opera

NUOVO PROGRAMMA: radice.py
ESERCIZIO 15.7 Programma passo passo
  1. All'inizio definite la precisione ε richiesta per il calcolo, ad esempio epsilon = 0.000001 (Python accetta anche la notazione esponenziale 1 x 10-6: potete scrivere epsilon = 1e−6, senza spazi tra i caratteri dell'esponente).
  2. Chiedete all'utente di immettere n, il numero (float) di cui cerchiamo la radice.
  3. Inizializzate le variabili b (la nostra radice) a 1 e h a n (cioè n / b): questi valori costituiranno la nostra "prima prova".
  4. Inizializzate anche una variabile contatore i a 0 (ci servirà per stampare i risultati intermedi)
  5. Ora mettete l'istruzione while abs(b - h) > epsilon: (ricordate che nel while dovete indicare le condizioni per ripetere il ciclo, non quelle per uscire!). Nel corpo del ciclo ...
  6. Calcolate il nuovo valore di b come abbiamo detto sopra e riassegnatelo a sè stessa
  7. Calcolate anche il nuovo valore di h (sempre come abbiamo detto sopra) e riassegnatelo a sè stessa: ora abbiamo in b ed h i nuovi valori
  8. Incrementate il contatore
  9. Stampate il messaggio "Passo x: valore yyy" (che variabili dovete scrivere in x ed y?). Qui finisce il corpo del while: Python controllerà di nuovo la distanza tra b ed h e ripeterà il procedimento se necessario.
  10. Alla fine, fuori del ciclo, stampate il valore trovato
Esempio di I/O:
Inserisci il numero 81
Passo 1 valore: 41.0
Passo 2 valore: 21.48780487804878
Passo 3 valore: 12.628692450375128
Passo 4 valore: 9.521329066772005
Passo 5 valore: 9.014272376994608
Passo 6 valore: 9.000011298790216
Passo 7 valore: 9.000000000007091
La radice trovata e' 9.000000000007091
SOLUZIONI

Lanciando il programma più volte con numeri diversi vedremo come i valori che trova nei vari passi si avvicinano effettivamente sempre più al valore esatto (il risultato, però, come vedete, è approssimato: potete renderlo più preciso diminuendo epsilon). Notiamo anche che la convergenza è piuttosto veloce: di solito il programma si ferma dopo meno di 10 iterazioni.

ESERCIZIO 15.8: Più controllo
Provate ad inserire un numero negativo e guardate cosa succede: in questo caso la successione dei b che otteniamo non è convergente ed il programma entra in un ciclo senza fine. Tutto questo è logico in quanto la radice quadrata di un numero negativo non esiste. Dopo che l'utente ha inserito il numero, bisogna quindi verificate che esso sia positivo: se lo è si può proseguire, altrimenti si avvisa l'utente che la radice del numero non esiste.
SOLUZIONI

UN ALTRO GIOCO

Hai chiesto la mano della figlia del Re, ma il padre, prima di concedertela, ti ha sottoposto ad una crudele prova. Sei al centro di un labirinto e per dieci volte dovrai scegliere tra una porta a sinistra ed una a destra: se aprirai quella giusta potrai proseguire, ma se sbaglierai troverai un mostro che ti ucciderà.

Nel gioco dovremo quindi indovinare una stringa composta casualmente da 10 caratteri "s" o "d" (ad esempio "ssdsddsdds" oppure "dddssdssdd") digitando un carattere per volta. Ogni volta che sbaglieremo dovremo ricominciare da capo (e quindi ricordare tutti i caratteri giusti già immessi). Per rendere la cosa più simpatica ogni volta che moriremo cercheremo di simulare un minimo di grafica con una stringa "artistica" come quelle che abbiamo già visto.

Nella prima parte del programma inizializzeremo le variabili che ci servono e daremo le istruzioni necessarie all'utente, poi dovremo programmare il gioco vero e proprio.

NUOVO PROGRAMMA: gioco_porte.py
ESERCIZIO 15.9 Programma passo passo
  1. Come al solito importate il modulo random ed inizializzate il generatore di numeri casuali
  2. Copiate questo codice nel programma
    stringa_inizio = """
    
    Sei imprigionato in un labirinto.
    Per 10 volte dovrai aprire una porta a sinistra o una a destra. Se sbaglierai scelta MORIRAI!
    Premi INVIO quando sei pronto
    
    """
    
    stringa_morte = """
    
    AAAARRRRGGGGHHHH!      SEI MORTO!
    
      ##
    ######
      ##
      ##
      ##
    
    Ma per fortuna puoi ricominciare!
    
    """

E' necessario soffermarci un po' sul passo 2, per farvi notare ancora una volta come i programmatori odiino inserire le costanti (numeri e stringhe) durante il programma: queste stringhe potrebbero tranquillamente essere scritte nei punti in cui dobbiamo stamparle (ad esempio: print("""Sei imprigionato ...), ma questo porterebbe ad una scarsa leggibilità (le stringhe si estendono su più righe e sarebbe addirittura difficile capire qual è l'istruzione successiva). Allora si preferisce assegnare il loro valore ad una variabile e scrivere poi print(stringa_inizio) che è molto più facile da leggere. Inoltre inizializzando queste variabili tutte insieme nella prima parte del programma è più facile ritrovarle se dovessimo decidere di cambiarle (ad esempio potreste sostituire la mia stringa_morte con una vostra creazione).

  1. Create una stringa porte inizialmente vuota
  2. Usando un ciclo for concatenate alla stringa (con l'operatore +=) dieci stringhe casuali "s" o "d". ATTENZIONE: per creare una stringa che sia casualmente "s" o "d" ci sono più tecniche differenti. Ripassate le funzioni nel modulo random qui)
  3. Ora inserite l'istruzione input(stringa_inizio) che stamperà la spiegazione del gioco ed aspetterà che voi premiate Invio: notate che in questo caso non c'è bisogno di assegnare il valore restituito a nessuna variabile

Provate il programma fin qui: dovrebbe stampare la spiegazione, attendere che premiate Invio e poi uscire. Ora l'utente deve immettere per 10 volte "s" o "d": se il valore sarà uguale a quello corrispondente nella stringa porte potrà andare avanti, altrimenti dovrà ricominciare da capo. Questo fatto introduce una complicazione: avremo naturalmente bisogno di un ciclo che conti le risposte esatte, ma questo dovrà ricominciare ogni volta che sbagliamo.

Per ottenere questo comportamento non possiamo usare il for, in quanto in esso Python incrementa automaticamente la variabile contatore e non permette di modificarla all'interno del ciclo. Questo è invece possibile con il while (con la variabile contatore): nel corpo del while inseriremo la domanda all'utente e la verifica: se la risposta sarà corretta il contatore sarà incrementato, altrimenti sarà resettato a 0 per ricominciare. Continuate così:

  1. Inizializzate il contatore i a 0
  2. Inserite l'istruzione while i < 10:
  3. Prendete in input la stringa risp stampando il prompt "Porta n. x (s/d)? "
    ATTENZIONE: ricordate che:
    • il prompt della input() deve essere un'unica stringa, ottenuta eventualmente concatenando più stringhe con l'operatore +
    • ci conviene far variare i da 0 a 9 (in modo che corrisponda agli indici della stringa porte che variano anch'essi da 0 a 9), ma nel prompt le porte dovranno essere numerate da 1 a 10
  4. Confrontate risp con la risposta giusta che si trova in porte[i] (ricordate questo)
  5. Se sono uguali stampate "Esatto!" ed incrementate il contatore per affrontare la prossima porta
  6. Altrimenti stampate stringa_morte e riportate il contatore a 0 per ricominciare. Qui finisce il corpo del ciclo while
  7. Fuori dal ciclo (si arriva qui quando si sono indovinate tutte le 10 porte) stampate un messaggio di congratulazioni
SOLUZIONI

Provate il programma. Per migliorare l'aspetto ludico possiamo misurare il tempo che il giocatore impiega a finire il gioco

ESERCIZIO 15.10 Misurazione del tempo
  1. All'inizio del programma importate anche il modulo time
  2. Subito prima di iniziare il ciclo inserite l'istruzione t1 = time() che ci da il tempo iniziale
  3. Immediatamente dopo la fine del ciclo inserite t2 = time() per avere il tempo finale. Il tempo impiegato (in secondi) sarà allora t2 - t1. Stampatelo nel messaggio finale
SOLUZIONI

A questo punto, come abbiamo visto nel programma indovina_numero.py, possiamo inserire anche la gestione dei record, che lascio a voi.

UNA CLASSE ED I SUOI VOTI

Riprendiamo l'esempio fatto all'inizio della Lezione 11. Abbiamo fatto notare che per risolvere molti problemi viene naturale l'uso di tipi di dati complessi, formati dall'aggregazione di più dati atomici. I linguaggi di programmazione hanno risposto a quest'esigenza con la programmazione orientata agli oggetti (OOP), che è però un argomento piuttosto avanzato e non tratteremo qui. In questa sede ci limiteremo a mostrare l'uso delle liste di liste (cioè liste i cui elementi sono a loro volta liste).

Immaginiamo di dover scrivere un programma che gestisca lo scrutinio finale di una classe: la classe sarà formata da vari studenti ed ognuno avrà i suoi voti. Per semplificare al massimo faremo le seguenti assunzioni:

Passiamo alla rappresentazione dei dati: potremo rappresentare uno studente con una lista di cinque elementi:

indice nella lista significato tipo di dato
0 cognome str (stringa)
1 nome str
2 voto in Italiano int (numero intero)
3 voto in Matematica int
4 esito dello scrutinio str ("p", "r" o "b")

Quindi, se a è un alunno, a[0] sarà il suo cognome, a[1] il suo nome, e così via. La classe sarà allora una lista di alunni (e quindi, come abbiamo detto, una lista di liste). Come facciamo allora a leggere il voto in Italiano del secondo alunno della classe? La sintassi è molto semplice, ed assomiglia a quella che si usa in Matematica per le matrici: il secondo alunno è l'elemento di indice 1 nella classe, e il suo voto in Italiano è l'elemento di indice 2 dell'alunno. Quindi dovremo scrivere classe[1][2] (cioè l'elemento 2 dell'elemento 1 della classe). Se vi sembra che tutto questo sia troppo complicato provate a fare un po' di pratica con IDLE

ESERCIZIO 15.11 Copiate ed incollate in IDLE la seguente lista:
classe = [ ["Abbotti", "Cesare", 5, 6, "r"], ["Bombetti", "Camilla", 9, 10, "p"],
["Casini", "Piergiorgio", 4, 3, "b"], ["De Rompis", "Anna", 7, 5, "r"] ]
Notate l'uso delle parentesi quadre una dentro l'altra per indicare che gli elementi della lista classe sono a loro volta delle liste. Ora provate a dare i seguenti comandi (ho omesso l'output) cercando di indovinare il risultato prima di premere Invio:
>>> classe[0][0]
>>> classe[0][1]
>>> classe[1][0]
>>> classe[1][1]
>>> classe[2][2]
>>> classe[2][3]
>>> classe[4][4]

Ecco adesso un programma che ci permette di inserire nome, cognome e voti di ogni alunno; volutamente, per il momento, trascuriamo l'esito dello scrutinio (lo farete voi come esercizio)

NUOVO PROGRAMMA: scrutinio_finale.py

materie = ["Italiano", "Matematica"]
classe = []                                        # la nostra classe, inizialmente vuota

risp = "s"
while risp == "s":                                 # ciclo per inserire gli alunni
    alunno = []
    print("Alunno", len(classe) + 1)               # stampa il numero dell'alunno corrente
    alunno.append(input("Inserisci il cognome "))  # piu' compatto, senza usare variabili intermedie
    alunno.append(input("Inserisci il nome "))
    for i in range(len(materie)):                  # al solito, non scriviamo range(2)
        alunno.append(int(input("Inserisci il voto in " + materie[i] + " ")))
    classe.append(alunno)                          # aggiungiamo l'alunno alla classe
    risp = input("Vuoi inserire un altro alunno?(s/n) ") 
ESERCIZIO 15.12 Ora vogliamo stampare un riassunto dei voti di ogni studente: usate un ciclo con il contatore i che iteri sugli indici della lista classe per ottenere un output simile a questo:
1 Abbotti    Cesare      Italiano 6      Matematica 5
2 Bombetti   Camilla     Italiano 9      Matematica 10
...
ATTENZIONE: Ottenere quest'output non è facile: dovete usare i doppi indici per riferirvi ai dati dei singoli alunni ed il carattere di tabulazione "\t" per allineare le varie parole. Purtroppo se introducete nomi lunghi e nomi corti (ad es. "Ugo" e "Alessandro") la tabulazione non si allineerà correttamente dandovi un risultato esteticamente brutto. Come ho detto, ci sono delle istruzioni Python per formattare le stringhe ma per il momento non ne parleremo. Può anche essere utile, anzichè scrivere una complicatissima print(), usare una variabile intermedia s da costruire man mano aggiungendo i vari dati.

Infine vediamo come risolvere il problema dell'esito dello scrutinio: questo dato non va immesso dall'utente ma deve essere ricavato dal programma stesso

ESERCIZIO 15.14 Modificate la prima parte del programma: il problema è simile a quello che abbiamo incontrato nell'ESERCIZIO 13.2: man mano che inseriamo i voti contiamo quelli insufficienti. Alla fine potremo decidere se l'alunno è stato promosso, rimandato o bocciato ed aggiungere in coda alla lista alunno la stringa appropriata. Modificate anche la parte del programma che stampa i risultati per includere questa nuova stringa.
SOLUZIONI
ESERCIZIO 15.13 (Per i più duri!): alla fine stampate gli alunni dividendoli in promossi, rimandati e bocciati (dovrete creare tre liste: promossi, rimandati, bocciati inizialmente vuote, ed inserirvi gli alunni ...

Fine della lezione