Fondamenti di Programmazione

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:

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'])