Prendere Decisioni
Nella scorsa lezione abbiamo visto il potente costrutto for
che permette di iterare l'esecuzione di un blocco di istruzioni quanto si vuole. Ma questo tipo di costrutti non sono sufficienti per implementare in un programma una qualsiasi procedura o algoritmo. Occorre la possibilità di prendere decisioni. Senza, non si può sostanzialmente scrivere nessun programma significativo.
Ogni decisione è presa in dipendenza del valore di una certa condizione. Ad esempio, per decidere se un numero è pari o dispari la condizione è se il resto della divisione per 2 è uguale a 0 o a 1, per decidere se in un elenco ordinato un nome debba essere posto prima di un'altro la condizione è se il primo è alfabeticamente minore del secondo, per decidere se la risposta a un quiz è giusta la condizione è se la risposta è uguale a quella memorizzata, per decidere se una stringa di 16 caratteri alfanumerici è un valido codice fiscale la condizione è se la stringa soddisfa o meno il formato per i codici fiscali, ecc. La condizione può essere semplice o complessa ma il suo valore sarà sempre o vero o falso. Se è vero si dovrà fare una certa cosa e se invece è falso un'altra. Queste condizioni sono dette Booleane dal nome del matematico George Boole che a metà dell'ottocento scrisse un saggio, "The Laws of Thought", in cui dimostrò che le leggi della logica potevano essere espresse in termini puramente matematici usando solamente i valori vero (true), falso (false) e tre operatori (detti appunto Booleani), and, or e not.
Operatori Booleani
Python, come molti altri linguaggi, ha un tipo per rappresentare i valori Booleani che si chiama bool
. Quindi bool
ha solamente due valori: True
e False
. Gli operatori Booleani sono:
not
: operatore unario,not x
èTrue
se e solo sex
èFalse
;and
: operatore binario,x and y
èTrue
se e solo sex
ey
sonoTrue
;or
: operatore binario,x or y
èTrue
se e solo se ox
èTrue
oy
èTrue
.
Gli operatori sono elencati in ordine di precedenza decrescente. Vediamo ora qualche esempio
>>> freddo = True
>>> caldo = not freddo
>>> caldo
False
>>> pioggia = False
>>> nuvoloso = True
>>> brutto_tempo = pioggia or nuvoloso
>>> brutto_tempo
True
>>> vento = True
>>> neve = True
>>> tormenta = vento and neve
>>> tormenta
True
Le espressioni Booleane possono essere arbitrariamente complesse:
>>> bel_tempo = not pioggia and not nuvoloso and not neve
>>> bel_tempo
False
>>> # Una espressione equivalente
>>> bel_tempo = not (pioggia or nuvoloso or neve)
>>> bel_tempo
False
Spesso, i valori Booleani sono il risultato di espressioni che effettuano confronti tra valori come ad esempio l'uguaglianza di due numeri o di due stringhe.
Operatori Relazionali
Gli operatori relazionali permettono di confrontare valori per i quali è possibile determinare se sono uguali o se uno è maggiore o minore di un altro. Gli operatori relazionali in Python sono i seguenti:
Simbolo | Operazione |
---|---|
< |
minore di |
> |
maggiore di |
<= |
minore o uguale di |
>= |
maggiore o uguale di |
== |
uguale a |
!= |
diverso da (si può anche scrivere <> ) |
Ecco alcuni esempi
>>> 3 < 5
True
>>> 10 == 11
False
>>> 10.0 == 10
True
>>> 3.5 != 3.45
True
Ovviamente possiamo combinare i vari operatori
>>> x = 2
>>> y = 5
>>> z = 4
>>> print "ordinati:", (x <= y and y <= z)
ordinati: False
Confronti come questi possono essere scritti in modo più compatto e più leggibile:
>>> print "ordinati:", (x <= y <= z)
ordinati: False
Anche le stringhe possono essere confrontate
>>> "stringa" == "Stringa"
False
>>> '0' < 'A' < 'a'
True
>>> 'stringA' < 'stringa' < 'stringaa'
True
I caratteri sono confrontati in base al loro codice (ASCII) così le cifre vengono prima delle maiuscole che vengono prima delle minuscole. Stringhe di più caratteri sono confrontate considerando i loro caratteri nell'ordine da sinistra verso destra: se tutti i caratteri sono uguali le due stringhe sono uguali; se una è il prefisso dell'altra quella più corta è la minore; altrimenti il primo carattere in cui le stringhe differiscono determina l'ordine.
Gli operatori relazionali sono applicabili a tutti i tipi sequenza, quindi anche alle liste:
>>> lst = [1, 2, 'abc', 5]
>>> lst == [1, 2, 'abc', 5]
True
>>> lst < [1, 2, 'abcd']
True
Il confronto è effettuato come per le stringhe considerando i valori delle liste da sinistra verso destra.
Operatore di appartenenza
Un'altro operatore molto utile per esprimere condizioni è l'operatore in
che permette di testare l'appartenenza di un valore a una sequenza:
>>> seq = [1, 2, 3, 5, 8]
>>> 5 in seq
True
>>> 4 in seq
False
>>> 'mela' in ['noce', 'mela', 'banana']
True
>>> 4 not in seq
True
Si può usare not in
se si vuole testare la non appartenenza. Chiaramente, x not in seq
è equivalente a not x in seq
.
Nelle stringhe l'operatore in
non solo permette di testare l'appartenenza dei singoli caratteri ma anche di sottostringhe:
>>> nozze = 'pizza e fichi'
>>> 'a' in nozze
True
>>> 'A' in nozze
False
>>> ' ' in nozze
True
>>> 'fichi' in nozze
True
>>> 'pizze' in nozze
False
Decisioni
In Python il costrutto che permette di prendere delle decisioni è l'if
che nella sua forma di base ha la seguente sintassi:
if condizione:
istruzioni
if
è una parola chiave, a seguire c'è una condizione
e dopo i :
ci sono le istruzioni
, indentate di 4 spazi, che saranno eseguite solo se il valore della condizione
è True
.
pioggia = False
nuvoloso = True
con = "senza"
if pioggia or nuvoloso:
con = "con"
print "usciamo " + con + " l'ombrello"
nuvoloso = False
con = "senza"
if pioggia or nuvoloso:
con = "con"
print "usciamo " + con + " l'ombrello"
Spesso per prendere una decisione bisogna controllare più condizioni e Python ha una forma estesa dell'if
che è molto utile in tali situazioni:
if condizione_1:
istruzioni_1
elif condizione_2:
istruzioni_2
elif condizione_3:
istruzioni_3
.
.
.
istruzione
elif
è una parola chiave e ce ne possono essere un numero arbitrario, tutte indentate allo stesso livello del primo if
. Se condizione_1
è True
, esegue istruzioni_1
e poi salta a eseguire istruzione
alla fine del blocco dell'if
, se invece condizione_1
è False
e condizione_2
è True
, esegue istruzioni_2
e poi salta alla fine del blocco if
, se condizioni_1
e condizione_2
sono False
e condizione_3
è True
, esegue istruzioni_3
e poi salta alla fine del blocco if
, e così via. Se nessuna delle condizioni è soddisfatta (cioè, nessuna è True
) passa a eseguire istruzione
dopo la fine del blocco dell'if
.
A volte è utile avere delle istruzioni che sono eseguite solo se nessuna delle condizioni dell'if
è soddisfatta, per questo c'è la parola chiave else
:
if condizione_1:
istruzioni_1
elif condizione_2:
istruzioni_2
.
.
.
else:
istruzioni_else
istruzione
Se nessuna delle condizioni è soddisfatta, esegue istruzioni_else
prima di saltare a istruzione
. Ecco un semplice esempio
def commenti_voto(voto):
print "Il voto e'", voto, ", ",
if voto < 18:
print "mi dispiace"
elif voto == 18:
print "appena sufficiente"
elif voto < 24:
print "OK, ma potevi fare meglio"
elif voto == 30:
print "congratulazioni!"
else:
print "bene!"
commenti_voto(15)
commenti_voto(18)
commenti_voto(23)
commenti_voto(27)
commenti_voto(30)
Nel seguente esempio si mostra l'utilità dell'operatore d'appartenenza che può sostituire un lungo blocco di if-elif
:
def frutto(x):
if x == 'mela':
return True
elif x == 'pera':
return True
elif x == 'uva':
return True
else:
return False
x = 'melo'
print x, "e' un frutto?", frutto(x)
def frutto2(x):
return x in ['mela', 'pera', 'uva']
print x, "e' un frutto?", frutto2(x)
x = 'uva'
print x, "e' un frutto?", frutto2(x)
Una nota molto importante: ovunque Python si aspetta una condizione Booleana (cioè un'espressione il cui valore è o True
o False
) come nella condizione di un if
, il valore dell'espressione sarà automaticamente interpretato come un valore booleano. Le convenzioni per tale interpretazione sono molto semplici: i valori 0
, 0.0
(zero per int
e float
) , ''
(la stringa vuota) e []
(la lista vuota) sono interpretati come False
e tutti gli altri sono interpretati come True
.
Esempi
Vogliamo scrivere una funzione che prende in input il nome abbreviato di un mese, cioè le prime tre lettere, e un giorno e ritorna True
se la data è corretta rispetto ad un anno non bisestile.
def check_date(mese, giorno):
if giorno < 1: return False
if mese == 'feb':
return giorno <= 28
elif mese in ['apr','giu','set','nov']:
return giorno <= 30
elif mese in ['gen','mar','mag','lug','ago','ott','dic']:
return giorno <= 31
else:
return False
def test(mese, giorno):
print 'mese:', mese, 'giorno:', giorno, check_date(mese, giorno)
test('gen', 31)
test('feb', 29)
test('dic', 0)
test('mir', 1)
test('gen', 1.5) # Questo test risulta True perche' non e'
# stato messo alcun controllo sul fatto che
# giorno deve essere un intero.
# Il controllo da aggiungere e'
# type(giorno) == int
Ora vogliamo scrivere una funzione che conta il numero di stringhe in una lista di lunghezza maggiore od uguale a un parametro k
.
def long_str(lst, k):
count = 0
for s in lst:
if len(s) >= k:
count += 1
return count
def test(lst, k):
print 'k =', k, 'lst =', lst
print long_str(lst, k)
lst = ['rosso','verde','giallo']
test(lst, 6)
test(lst + ['blu','arancione'], 4)
Adesso vogliamo scrivere una funzione che ritorna l'indice del primo valore di una lista che non è compatibile con un ordinamento ascendente della lista (cioè, c'è un valore successivo che è minore di esso).
def nosort(lst):
for i in range(len(lst)):
for j in range(i + 1, len(lst)):
if lst[j] < lst[i]:
return i
return -1
def test(lst):
print lst, 'nosort =', nosort(lst)
test([1,2,3,2])
test(['abc','abcc','ab'])