Python/Iterazione
Python ha solo l'istruzione while per implementare l'iterazione nel senso di altri linguaggi come il Pascal o il C:
while <condizione>:
<istruzioni>
[else:
<istruzioni>]
Mentre l'istruzione for permette di iterare sugli elementi di una sequenza:
for <variabile> in <sequenza>:
<istruzioni>
[else:
<istruzioni>]
Entrambi i cicli ammettono due istruzioni:
- continue che permette di saltare le rimanenti istruzioni del ciclo passando all'iterazione successiva,
- break che termina il ciclo
- se è presente la clausola else, le sue istruzioni vengono eseguite se il ciclo termina normalmente, mentre non vengono eseguite se il ciclo termina a causa dell'istruzione break.
while
modificaL'istruzione while permette di ripetere un blocco di istruzioni fin quando si mantiene vera una certa condizione.
Ad esempio se vogliamo elencare i primi 10 numeri naturali:
cont=0
while cont < 10:
print cont
cont+=1
Per elencare i primi 10 numeri pari:
cont=0
while cont < 10:
print cont*2
cont+=1
Ma in generale while va usato quando non si conosce il numero di volte che il ciclo andrà ripetuto. Ad es. nella seguente successione l'uso di while è adeguato:
n=7
while n!=1:
print n
if n % 2 == 0:
n/=2
else:
n=n*3+1
Una delle tante implementazioni del Crivello di Eratostene usa un paio di cicli while:
def crivello(n):
"""Restituisce i numeri primi inferiori a n."""
c=range(2, n+1) # crea una lista con tutti i numeri
i=0 # inizializza l'indice
n=len(c) # calcola l'ultimo numero da processare
nr=n**0.5 # calcola l'ultimo numero da processare
while i<nr: # fin quando l'indice è minore di questo numero
if c[i]: # controlla che il numero puntato da i sia diverso da 0
j=i+1 # in questo caso inizializza j al valore successivo di i
while j<n: # fin quando j è minore di n
if c[j] % c[i] == 0: c[j]=0 # se l'elemento è divisibile per c[i] cambialo a zero
j+=1 # incrementa j
i+=1 # incrementa i
c=[e for e in c if e] # crea una nuova lista fatta dagli elementi non nulli di c
return c # restituisci questa lista che contiene solo numeri primi
for
modificaL'istruzione for è molto diversa dall'omonima istruzione del Pascal o del C, non è un'istruzione di ciclo, ma è un iteratore su una sequenza.
Se a è array di stringhe, e voglio visualizzarle una sotto l'altra, in Pascal scriverò all'incirca:
...
for i:=1 to maxarray do
writeln(a[i]);
...
In Python:
for elemento in a:
print elemento
Durante l'esecuzione del ciclo for la variabile elemento viene collegata ad ognuno degli elementi della sequenza a: non servono indici, non serve sapere quanti sono gli elementi della sequenza, quando sono finiti termina il ciclo.
Ad esempio una funzione che sommi tutti gli elementi di una lista di numeri potrebbe essere:
def somma(lista):
s=0
for num in lista:
s+=num
return s
>>> l=[1, 3, 5, 7, 9]
>>> print somma(l)
25
Ora se volessi la media dei numeri contenuti in una lista, posso scrivere la funzione:
def media(lista):
return somma(lista)/float(len(lista))
>>> print media(l)
5.0
Peccato solo che che, per questioni di efficienza, Python abbia implementato tra le funzioni native la funzione add che produce lo stesso risultato della nostra funzione somma.
Se volessi implementare una ricerca sequenziale in una lista, potrei usare for nel seguente modo:
def contenuto(elemento, lista):
result=True
for e in lista:
if e==elemento:
break
else:
result=False
return result
>>> print contenuto('pippo', l)
True
>>> print contenuto('Pippo', l)
False
Se volessi avere non una funzione che mi dice semplicemente se l'elemento è contenuto in una lista ma qual è la sua posizione, potrei combinare l'iteratore for con la funzione enumerate che restituisce la coppia: posizione, elemento:
def posizione(elemento, lista):
for i, e in enumerate(lista):
if e==elemento:
result=i
break
else:
result=-1
return result
>>> print posizione('pluto', l)
1
>>> print posizione('pippo', l)
0
>>> print posizione('Pippo', l)
-1
Anche qui peccato che Guido ci abbia preceduti e sempre per questioni di efficienza abbia implementato l'istruzioine in e il metodo index che fanno all'incirca quello che fanno le nostre funzioni contenuto e posizione.
for: la trappola
modificaL'iteratore for presenta un trabocchetto... Per capirlo, partiamo da un esempio. Abbiamo una lista che contiene vari numeri, vogliamo togliere tutti quelli pari:
def toglipari(lista):
for e in lista:
if e%2==0:
lista.remove(e)
>>> num=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> toglipari(num)
>>> print num
[1, 3, 5, 7, 9]
Sembra funzionare perfettamente, ma non è così! Se tentiamo di togliere i numeri pari da quest'altra lista:
>>> num1=[0, 1, 2, 4, 5, 6, 7, 8, 9]
>>> toglipari(num1)
>>> num1
[1, 4, 5, 7, 9]
otteniamo un risultato sconcertante!
Cosa è successo, come mai a volte funziona e a volte no? Questi sono gli errori più insidiosi, appaiono solo a volte, e, tipicamente, quando fanno più danno.
Il fatto è che si deve assolutamente evitare di modificare la sequenza su cui l'iteratore for sta lavorando.
Nel primo caso (num=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) viene controllato il primo elemento di num, è pari e viene tolto. Ora il primo elemento è 1 e viene controllato il secondo, vale 2, è pari e viene tolto, ora il primo elemento è 1 e il secondo è 3, viene controllato il terzo che è pari e viene tolto...
Nel secondo caso (num1=[0, 1, 2, 4, 5, 6, 7, 8, 9]) viene controllato il primo elemento di num1, è pari e viene tolto. Ora il primo elemento è 1 e viene controllato il secondo, vale 2, è pari e viene tolto, ora il primo elemento è 1 e il secondo è 4, viene controllato il terzo che è 5, è dispari e viene lasciato, poi viene controllato il quarto elemento...
Regola fondamentale per non cadere nella trappola è: "mai modificare una lista mentre si sta iterando su di essa".
Allora, come si può realizzare l'operazione precedente? Basta iterare su una lista e modificarne un'altra, inizialmente identica. Ma come costruire una lista identica ad una già presente? Ci sono due costrutti Python che permettono di farlo:
lista1=lista[:] # lista1 contiene tutti gli elementi di lista
lista2=list(lista) # lista2 è una copia di lista
Tenendo conto di questo si può riscrivere il comando precedente e provare che funziona:
def toglipari(lista):
for e in lista[:]:
if e%2==0:
lista.remove(e)
>>> num=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> toglipari(num)
>>> print num
[1, 3, 5, 7, 9]
>>> num1=[0, 1, 2, 4, 5, 6, 7, 8, 9]
>>> toglipari(num1)
>>> print num1
[1, 5, 7, 9]
>>> num2=[0, 1, 3, 2, 6, 4, 5, 2, 7, 8, 2, 9]
>>> toglipari(num2)
>>> print num2
[1, 3, 5, 7, 9]