Archivio tag: Cicli iterativi

Realizzare un ciclo iterativo con l’istruzione repeat

Abbiamo già visto come realizzare un ciclo iterativo in R sfruttando le istruzioni for e while. Se il primo ciclo è definito da un numero predeterminato di iterazioni, il secondo è invece potenzialmente infinito e sarà la condizione logica espressa al momento del lancio a stabilire il criterio d’arresto. Di solito, il ciclo for si usa quando già prima della sua esecuzione è noto il numero di iterazioni da eseguire, mentre il ciclo while non richiede questa conoscenza: se all’interno del ciclo accade qualcosa che soddisfa la condizione d’arresto, allora il while si chiuderà.

Per comprendere il terzo ciclo, il repeat, è necessario aver ben digerito i primi due, che consiglio di andare a rivedere nel caso di dubbi (qui e qui).

Il ciclo repeat è, come il while, potenzialmente infinito. La differenza è che, rispetto al while, il repeat è ancora meno restrittivo perché non richiede di specificare alcuna condizione d’arresto; se non si presta molta attenzione, infatti, si rischia veramente di far perdere l’interprete R all’interno di un loop infinito! In questi casi, solo la brutale chiusura del programma potrà fermare il ciclo.

Utilizzare l’istruzione repeat è semplicissimo (attenzione: il codice qui sotto genera un ciclo infinito):

repeat {
    # Codice da eseguire
}

Fra i tre cicli iterativi di R, il repeat è certamente il più flessibile: l’arresto è determinato esclusivamente da quello che accade al suo interno. La vera sfida nell’uso di repeat non è tanto comprendere come funziona il comando quanto piuttosto capire come fermarlo!

Per controllare l’evolversi delle iterazioni, R mette a disposizione due istruzioni che sono next e break. La prima interrompe una iterazione per saltare direttamente alla successiva, mentre la seconda ferma tutto e chiude definitivamente il ciclo.

Per esempio, il seguente codice lancia un ciclo di iterazioni che vengono immediatamente bloccate con la conseguente uscita dal loop:

repeat {
    break
}

Ovviamente, questo non è sufficiente per utilizzare repeat in maniera proficua. Noi dobbiamo essere in grado non solo di bloccare le iterazioni ma anche di controllare quando bloccarle. Per questo motivo, le istruzioni next e break devono essere combinate con altre due istruzioni, che sono if ed else. Queste indicano a R cosa fare “se” (if) si presenta una certa situazione, e cosa fari “altrimenti” (else), nel caso in cui la condizione espressa dalla if non fosse verificata.

Dato che vedere il codice in azione è sempre più eloquente di mille spiegazioni, vediamo di utilizzare un po’ questi comandi per comprenderli meglio.

Cerchiamo di realizzare un ciclo di dieci iterazioni utilizzando l’istruzione repeat. Il ciclo non dovrà fare altro che stampare a video il numero di iterazioni eseguite, e bloccarsi al termine della decima. Ecco quindi il codice che dovremo utilizzare:


i <- 0 repeat { i <- i+1 print(i) if(i == 10) { break } } [/code]

Analizziamolo. Abbiamo inizializzato la variabile i col valore zero. Dopodiché siamo entrati nel ciclo e abbiamo aggiornato il valore di i, incrementandolo di uno. Questa operazione verrà eseguita a ogni iterazione, in modo che i conti quante iterazioni sono state eseguite sino a quel momento. Quindi, con il comando print, stampiamo a video il valore di i.

A questo punto viene la parte difficile: dobbiamo verificare a quale iterazione siamo arrivati, ovvero qual è il valore di i, perché, se siamo arrivati a 10, dobbiamo arrestare il ciclo. Per farlo ci serviamo dell’istruzione if, che verifica se i è uguale a 10.

Attenzione! if è un comando che valuta la verità di una condizione logica: se la condizione è vera, allora il codice inserito all’interno delle sue parentesi graffe verrà eseguito, altrimenti, se c’è un else consecutivo verranno eseguite le operazioni interne alla else. Se dopo la if non ci dovesse essere un else (come nel nostro caso), semplicemente l’esecuzione del codice proseguirà indisturbata. Facciamo subito un esempio:


i <- 0 repeat { i <- i+1 print(i) if(i == 10) { break } else { print("Via al prossimo ciclo!") } } [/code]

Qui sopra è stata aggiunta una condizione alternativa alla if che stampa a video una stringa nel caso i non dovesse valere 10.

Ora immaginiamo di voler saltare il secondo ciclo; vogliamo sì arrivare al decimo, ma senza eseguire il secondo. È in questi casi che entra in gioco l’istruzione next:


i <- 0 repeat { i <- i+1 if(i == 2) { next } else { print(i) if(i == 10) { break } } } [/code]

Il codice qui sopra, non appena troverà che i vale due, salterà direttamente alla terza iterazione, altrimenti proseguirà con le operazioni. Quando si iniziano ad annidare istruzioni if ed else il rischio di scordarsi la chiusura di una parentesi è sempre molto elevato, ed è per questo che ho scelto di indentare il codice.

Si faccia attenzione che, quando il corpo dell’istruzione if contiene un’unica riga di codice, le parentesi graffe possono anche essere omesse (lo stesso vale per else).


i <- 0 repeat { i <- i+1 if(i == 2) { next } else { print(i) if(i == 10) break } } [/code]

A questo punto non mi resta che augurarvi buon divertimento!

Il ciclo iterativo while

Qualche tempo fa abbiamo parlato dei cicli iterativi e in particolare del ciclo for. I cicli sono una componente fondamentale di un linguaggio di programmazione e consentono di ripetere per un certo numero di volte una sequenza di istruzioni. Definita una variabile contatore e definiti n valori che tale variabile dovrà assumere, Il ciclo for ripete un blocco di istruzioni, aggiornando ciclo dopo ciclo il valore della variabile contatore finché questa non avrà assunto tutti i possibili valori.

Il ciclo while invece ha una logica un po’ diversa. Potenzialmente, un ciclo while potrebbe anche essere infinito perché non è richiesta alcuna variabile contatore: sarà il programmatore a stabile se è opportuno utilizzarla o meno. L’unica cosa che richiede il ciclo while è una condizione logica: finché questa sarà soddisfatta, il codice continuerà a essere ripetuto.


i <- 0 while(i == 0) print(i) [/code]

Nell’esempio qui sopra è stata creata una variabile i che contiene il valore 0. La condizione logica espressa nel ciclo while impone che l’istruzione prosegua nelle iterazioni finché i sarà uguale a 0. Dato che all’interno del ciclo i non viene in alcun modo alterata, l’istruzione while andrà avanti all’infinito rendendo necessario l’arresto forzato di R.

Se invece volessimo realizzare solo 100 iterazioni, dovremmo fare in modo che, all’interno del ciclo, i venga di volta in volta aggiornata, diventando così un vero e proprio contatore: quanto i varrà 100, il ciclo si chiuderà.


i <- 0 while(i < 100) { i <- i+1 print(i) } [/code]

Il ciclo qui sopra quindi non è infinito ma realizza 100 iterazioni. La potenza dell’istruzione while sta però proprio nel fatto di togliere al programmatore l’onere di stabilire in partenza il numero di iterazioni (per quello esiste già il ciclo for!). Può capitare infatti che il programmatore non abbia un’idea precisa di quante iterazioni occorrano per raggiungere un certo risultato; in questi casi sarà il codice a dover “capire” quando è giunta l’ora di concludere il ciclo.

Proviamo a fare un esempio pratico. Dobbiamo creare un ciclo while che, dato un certo numero x, a ogni iterazione tolga a x la sua radice quadrata; il ciclo si dovrà fermare quando x sarà inferiore a 1. Di seguito è riportato il codice per x = 100.


x <- 100 while(x >= 1)
x <- x-sqrt(x) [/code]

Dopo aver definito il valore x, il ciclo while viene avviato; esso parte (e prosegue) solo se x è maggiore di 1. Nel corso delle iterazioni x viene di volta in volta aggiornato, sottraendogli la sua radice quadrata. Il ciclo si chiuderà quando la condizione logica in apertura restituirà FALSE, cosa che capiterà soltanto quando x sarà minore di 1.

Possiamo aggiungere un vincolo al ciclo, facendo in modo che il numero di iterazioni non superi mai un certo valore; questa è una strategia che viene utilizzata spesso negli algoritmi di ottimizzazione, i quali a un certo punto devono concludersi anche se la soluzione del problema di ottimizzazione non è stata individuata. Nel nostro caso possiamo imporre che il ciclo non ecceda le 10 iterazioni. Per fare questo avremo bisogno di una variabile contatore che, aggiornata iterazione dopo iterazione, tenga traccia del numero di cicli eseguiti.


x <- 100 i <- 0 while(x >= 1 & i < 10) { i <- i+1 x <- x-sqrt(x) } [/code]

Il ciclo riportato qui sopra terminerà ben prima che la variabile x sia diventata inferiore a 1. Infatti, quando il contatore i avrà raggiunto il valore 10, il ciclo si dovrà in ogni caso arrestare e questo a prescindere dal valore di x. Vale però anche il contrario: il ciclo potrà fermarsi ben prima delle 10 iterazioni se x sarà giunto a un valore inferiore a 1. Difatti, perché la condizione x >= 1 & i < 10 risulti vera, x deve essere maggiore o uguale a 1 e contemporaneamente i minore di 10. Quando anche solo una delle due variabili non dovesse rispettare la condizione posta, il ciclo terminerà.

Il ciclo while è un’istruzione fondamentale quando si utilizza R per creare del codice “intelligente”, cioè scritto in modo che sia in grado di valutare autonomamente delle condizioni e decidere quanto spingersi nella soluzione di un problema. Forse nell’uso quotidiano di R è poco applicato (io di solito preferisco il for) ma ci sono casi in cui questa istruzione diventa davvero imprescindibile.

Iterare una sequenza di istruzioni: il ciclo for

L’iterazione è la ripetizione programmata di una porzione di codice, che può andare avanti finché non si presentano delle particolari condizioni. In R, come in tanti altri linguaggi di programmazione, sono previste diverse istruzioni in grado di realizzare un ciclo iterativo; le tre principali sono: for, while e repeat.

Ho sempre pensato che il saper usare i cicli iterativi sia una di quelle competenze che segnano una netta linea di demarcazione fra l’uso di R come semplice strumento di analisi statistica e l’uso di R come linguaggio di programmazione. Si tratta di un aspetto che forse a un informatico potrebbe apparire scontato, ma per me, che informatico non sono, è stato un qualcosa che ha cambiato completamente il mio modo di utilizzare R, aprendomi delle prospettive decisamente nuove. La possibilità di utilizzare i cicli iterativi rende R uno strumento estremamente flessibile e con il quale è possibile fare qualcosa di più che analizzare dati: con R è possibile creare con i dati.

Per chi non nasce come informatico e nel corso nella sua carriera di studente o di professionista non ha mai avuto a che fare con un linguaggio di programmazione, quello con i cicli iterativi può essere uno scontro molto duro (per me lo è stato). In questo post vedremo di studiare il funzionamento del primo dei cicli iterativi: l’istruzione for.

Questa è la forma generale che assume un ciclo for:

for(var in seq) {
    # Codice da ripetere
}

Il ciclo è costituito da due sezioni: l’iniziale dichiarazione della condizione e il corpo di istruzioni da ripetere. Se la condizione è racchiusa tra parentesi tonde, il codice soggetto a iterazione è racchiuso tra parentesi graffe.

Con la dichiarazione “var in seq” stiamo chiedendo a R di costruire una variabile di nome var e di farle assumere sequenzialmente tutti i valori contenuti nel vettore seq. Ipotizzando che seq sia un vettore costituito dagli elementi 3, 3, 4 e 5, var assumerà prima il valore 3, poi nuovamente il valore 3, poi 4 e infine 5. Il numero di iterazioni del ciclo è determinato dalla lunghezza del vettore seq, perché una volta che var ne avrà assunto uno dopo l’altro tutti i valori, il ciclo si concluderà.

Il seguente codice esegue esattamente quanto descritto, stampando a video, iterazione dopo iterazione, il valore assunto dalla variabile i (detta variabile contatore).

for(i in c(3,3,4,5)) {
    print(i)
}

Se il corpo del codice da iterare è costituito da una sola riga, le parentesi graffe possono anche essere omesse.

Facciamo ora qualcosa di più complicato. Dato un vettore x che contiene otto elementi (vedi sotto), vogliamo che a ognuno di essi venga moltiplicato prima il valore 1, poi il valore 2, poi 3, poi 4 e infine 5; a seguito di ogni incremento in x, tutti i valori contenuti nel vettore dovranno essere sommati e il risultato inserito in un altro vettore che conterrà tutti i risultati.

Come primo passaggio, dobbiamo costruire sia il vettore x che un secondo vettore che conterrà tutti i valori che risulteranno dall’espressione sottoposta a iterazione. Dato che dovremo effettuare 5 iterazioni (i dovrà assumere i valori da 1 a 5) i risultati dell’espressione saranno 5, per cui il vettore di risultati dovrà essere in grado di contenere 5 valori.


x <- c(8, 14, 7, 2, 5, 10, 2, 9) res <- rep(NA, 5) [/code]

L’oggetto res – results – contiene cinque NA, che iterazione dopo iterazione il ciclo for dovrà rimpiazzare con i risultati dell’espressione.

Vediamo quindi come realizzare il ciclo:


for(i in 1:5)
res[i] <- sum(x*i) [/code]

Per ogni iterazione, ogni valore in x sarà moltiplicato con l’attuale valore assunto da i; tutti i valori saranno sommati e il risultato verrà salvato nella i-esima posizione del vettore res. Questo è il risultato:

> res
[1]  57 114 171 228 285

Per svolgere questa operazione avremmo anche potuto non utilizzare il ciclo for ma scrivere “manualmente” tutto il codice necessario:


res[1] <- sum(x*1) res[2] <- sum(x*2) res[3] <- sum(x*3) res[4] <- sum(x*4) res[5] <- sum(x*5) [/code]

L’uso dei cicli iterativi richiede però un minor numero di righe di codice, e questo è un indubbio vantaggio. Nel nostro esempio siamo a due righe contro cinque, ma si pensi ai casi in cui si vuole ripetere la stessa operazione su dataset con decine di colonne o centinaia (se non migliaia) di unità statistiche.

Vorrei chiudere con qualche esempio pratico in cui la possibilità di utilizzare un ciclo iterativo ha giocato un ruolo determinante nel salvarmi la vita (sono drammatico, lo so, ma leggendo capirete…).

  • Tempo fa stavo lavorando su un questionario di valutazione psicologica. Il questionario conteneva 51 item, codificati in altrettante colonne di un dataset. Ogni colonna conteneva dei dati mancanti che dovevo rimpiazzare. Il ciclo for mi è costato due righe di codice contro le cinquantuno che altrimenti avrei dovuto scrivere per sostituire quei fastidiosi NA.

  • L’ISTAT organizza i dati del bilancio demografico suddividendoli per provincia; vengono annualmente distribuiti 110 file, tanti quante sono le province. Io avevo la necessità di assemblare tutti i file in un unico dataset. Realizzando un ciclo for in cui i assumeva di volta in volta il nome del file da importare, ho avuto la possibilità di unire 110 dataset in uno unico con pochi passaggi. Provate solo a immaginare cosa sarebbe stato unire manualmente 110 file diversi…