Ciclo While in MATLAB

Il ciclo while in MATLAB rappresenta una delle strutture di controllo più potenti e versatili per la programmazione condizionale.

Questa lezione esplora l'utilizzo del ciclo while per ripetere azioni basate su condizioni logiche, gestire input utente, leggere dati da file e altre applicazioni.

Il ciclo while

L'istruzione while è utilizzata come ciclo condizionale in MATLAB; serve per ripetere un'azione quando, in anticipo, non si sa quante volte tale azione verrà ripetuta.

La forma generale dell'istruzione while è:

while condizione
    azione
end

L'azione, che può consistere in un qualsiasi numero di istruzioni, viene eseguita finché la condizione risulta vera (true).

Il funzionamento è il seguente:

  • Inizialmente la condizione viene valutata;
  • Se risulta logicamente vera, l'azione viene eseguita. Quindi, al termine, la condizione viene valutata di nuovo.
  • Se è ancora vera, l'azione viene eseguita di nuovo. E così via, fino a quando qualcosa nell'azione modifica la condizione, rendendola falsa.

Prima o poi la condizione deve diventare falsa, altrimenti il ciclo non terminerà mai (loop infinito). (Se dovesse accadere, è possibile interrompere il ciclo con Ctrl+C).

Come esempio di ciclo condizionale, scriveremo una funzione che trovi il primo fattoriale maggiore del parametro di ingresso alto.

Per un numero intero n, il fattoriale di n, scritto come n!, è definito come n! = 1 \cdot 2 \cdot 3 \cdot 4 \cdot \dots \cdot n.

Per calcolare un fattoriale, normalmente si userebbe un ciclo for. Tuttavia, in questo caso non conosciamo in anticipo il valore di n, quindi dobbiamo continuare a calcolare il fattoriale successivo finché non si raggiunge un certo livello, il che significa usare un ciclo while.

L'algoritmo di base è avere due variabili: una che itera attraverso i valori 1, 2, 3 e così via, e una che memorizza il fattoriale del valore della variabile iteratrice a ogni passo.

Iniziamo con 1 e con il fattoriale di 1, cioè 1. Verifichiamo il fattoriale: se non è maggiore di alto, la variabile iteratrice aumenta di 1 (passando a 2) e calcoliamo il suo fattoriale (2). Se anche questo non è maggiore di alto, l'iteratore passa a 3 e otteniamo il fattoriale di 3 (6). Continuiamo così finché non troviamo il primo fattoriale che sia maggiore di alto.

Di conseguenza, il processo di incrementare una variabile e calcolarne il fattoriale si ripete finché non otteniamo il primo valore maggiore di alto. Questo viene implementato usando un ciclo while nel file fattoriale_maggiore_di.m:

function risultato = fattoriale_maggiore_di(alto)
    % fattoriale_maggiore_di restituisce il primo fattoriale > input
    % Formato: fattoriale_maggiore_di(inputInteger)
    i = 0;
    fat = 1;
    while fat <= alto
        i = i + 1;
        fat = fat * i;
    end
    risultato = fat;
end

Un esempio di chiamata di questa funzione, passando 5000 come valore del parametro di ingresso alto, è:

>> fattoriale_maggiore_di(5000)
ans =

   5040

La variabile iteratrice i è inizializzata a 0, e la variabile fat, che memorizzerà il fattoriale di ogni valore di i, è inizializzata a 1.

La prima volta che il ciclo while viene eseguito, la condizione è: i è minore o uguale a 5000? (in realtà controlla fat <= 5000). All'inizio è vero, quindi l'azione del ciclo viene eseguita: i diventa 1 e fat diventa 1 (1 * 1).

Dopo l'esecuzione dell'azione del ciclo, la condizione viene rivalutata. Poiché è ancora vera, l'azione viene eseguita di nuovo: i viene incrementato a 2 e fat assume il valore 2 (1 * 2). Il valore 2 è ancora minore o uguale a 5000, quindi il ciclo continua: i diventa 3 e fat diventa 6 (2 * 3).

Questo prosegue finché il primo valore di fat trovato è maggiore di 5000. Non appena fat raggiunge tale valore, la condizione diventa falsa e il ciclo while termina. A quel punto, il fattoriale viene assegnato al valore di uscita della funzione.

Il motivo per cui i è inizializzato a 0 anziché a 1 è che la prima volta che il ciclo viene eseguito, i diventa 1 e fat diventa 1, così abbiamo 1 e 1!, che vale 1.

Condizioni multiple in un ciclo while

Nella funzione fattoriale_maggiore_di, la condizione del ciclo while consisteva in un'unica espressione che verificava se la variabile fat era minore o uguale alla variabile alto.

In molti casi, tuttavia, la condizione potrebbe essere più complessa e usare l'operatore or (||) o l'operatore and (&&). Ad esempio, si potrebbe voler rimanere in un ciclo while finché una variabile x rimane in un certo intervallo:

while x >= 0 && x <= 100
    ...
end

Come altro esempio, può essere utile continuare l'azione di un ciclo finché almeno una di due variabili soddisfa una certa condizione:

while x < 50 || y < 100
    ...
end

In altre parole la condizione di un ciclo while può essere una qualunque espressione booleana più o meno complessa.

Lettura da un file usando un ciclo while

L'esempio seguente mostra come leggere dati da un file di dati utilizzando un ciclo while. I dati di un esperimento sono stati registrati in un file chiamato experiment_data.dat.

Il file contiene alcuni valori di peso, seguiti da un -99 e poi ancora altri pesi, tutti sulla stessa riga. Gli unici valori di dati che ci interessano, tuttavia, sono quelli precedenti al -99. Il -99 è un esempio di sentinella (sentinel), ossia un segnaposto che separa diverse serie di dati.

L'algoritmo per lo script è il seguente:

  • Leggere i dati dal file in un vettore.
  • Creare un nuovo vettore, newvec, che contenga solo i valori fino a (ma non compreso) il -99.
  • Tracciare i nuovi valori del vettore, usando cerchi neri.

Ad esempio, se il file contiene:

3.1 11 5.2 8.9 -99 4.4 62

Per semplicità, assumeremo che il formato del file sia come specificato. Usando il comando load si creerà un vettore chiamato experiment_data, che contiene i valori letti dal file.

Per realizzare lo script, si itera lungo il vettore fino a quando non si incontra il valore -99, costruendo il nuovo vettore memorizzando ogni elemento di experiment_data nel vettore newvec.

% leggi_dati.m

% Legge i dati da un file, ma traccia solo i numeri
% fino a incontrare -99. Usa un ciclo while.

load experiment_data.dat

i = 1;
while experiment_data(i) ~= -99
    newvec(i) = experiment_data(i);
    i = i + 1;
end

plot(newvec,'ko')
xlabel('Lettura Numero')
ylabel('Peso (kg)')
title('Primo Set di dati')

Si noti che così il vettore newvec si estende a ogni esecuzione dell'azione nel ciclo.

Esiste un modo più efficiente per realizzare lo script di sopra. Utilizzando la funzione find, possiamo individuare l'indice dell'elemento che contiene -99. In questo modo, il nuovo vettore comprenderà tutti gli elementi del vettore originale, dal primo elemento fino all'indice che precede quello che contiene -99.

% trova_valore.m

% Legge i dati da un file, ma traccia solo i numeri
% fino a incontrare -99. Usa find e l'operatore intervallo.

load experiment_data.dat
where = find(experiment_data == -99);
newvec = experiment_data(1:where-1);

plot(newvec,'ko')
xlabel('Lettura Numero')
ylabel('Peso (kg)')
title('Primo Set di dati')

Input in un ciclo while

A volte un ciclo while viene utilizzato per elaborare l'input da parte dell'utente finché l'utente inserisce dati in un formato corretto.

Lo script seguente ripete il processo di chiedere all'utente un numero positivo, leggerlo e ristamparlo, finché l'utente non digita un numero negativo. Non appena l'utente digita un numero negativo, lo script stampa "OK" e termina.

% whileposnum.m

% Chiede all'utente un numero e lo ristampa
% finché non viene inserito un numero negativo

inputnum = input('Inserisci un numero positivo: ');
while inputnum >= 0
    fprintf('Hai inserito %d.\n\n', inputnum)
    inputnum = input('Inserisci un numero positivo: ');
end
fprintf('OK!\n')

Quando lo script viene eseguito, l'input/output potrebbe essere simile a questo:

>> whileposnum
Inserisci un numero positivo: 6
Hai inserito 6.

Inserisci un numero positivo: -2
OK!

Si noti che l'invito (prompt) viene ripetuto nello script: una volta prima del ciclo e poi ancora alla fine dell'azione.

Questo accade perché, a ogni rivalutazione della condizione, servono nuovi valori di inputnum da verificare. Se l'utente inserisce un numero negativo al primo tentativo, non verrebbe ristampato alcun valore:

>> whileposnum
Inserisci un numero positivo: -33
OK!

Questo esempio illustra una caratteristica molto importante dei cicli while: è possibile che l'azione non venga eseguita nemmeno una volta, se la condizione risulta falsa la prima volta che viene valutata.

Come visto in precedenza, MATLAB restituirà un messaggio di errore se si digita un carattere anziché un numero:

>> whileposnum
Inserisci un numero positivo: a
Error using input
Unrecognized function or variable 'a'.

Error in whileposnum (line 4)
inputnum = input('Inserisci un numero positivo: ');

Inserisci un numero positivo: -4
OK!

Tuttavia, se il carattere digitato coincide con il nome di una variabile esistente, verrà usato il valore di quella variabile come input:

>> a=5;
>> whileposnum
Inserisci un numero positivo: a
Hai inserito 5.

Inserisci un numero positivo: -4
OK!

Estendere un vettore

Se si desidera memorizzare tutti i numeri positivi inseriti dall'utente, è possibile aggiungerli uno alla volta a un vettore.

Tuttavia, poiché non sappiamo in anticipo quanti elementi serviranno, non possiamo pre-allocare la dimensione corretta. I due metodi per estendere un vettore un elemento alla volta sono mostrati qui di seguito.

Possiamo iniziare con un vettore vuoto e concatenare a ogni iterazione un nuovo valore, oppure incrementare un indice.

numvec = [];
inputnum = input('Inserisci un numero positivo: ');
while inputnum >= 0
    numvec = [numvec inputnum];
    inputnum = input('Inserisci un numero positivo: ');
end

Oppure, possiamo utilizzare un indice per memorizzare i valori:

i = 0;
inputnum = input('Inserisci un numero positivo: ');
while inputnum >= 0
    i = i + 1;
    numvec(i) = inputnum;
    inputnum = input('Inserisci un numero positivo: ');
end

Si tenga presente che entrambi i metodi non sono efficienti e dovrebbero essere evitati se il vettore può essere pre-allocato.

Contare in un ciclo while

Anche se i cicli while vengono utilizzati quando non si conosce in anticipo quante volte l'azione verrà ripetuta, spesso è utile sapere quante volte l'azione è stata effettivamente eseguita.

In tal caso, è necessario contare il numero di volte in cui l'azione viene eseguita. La seguente variazione sullo script precedente conta quanti numeri positivi l'utente inserisce con successo.

% conta_numeri_positivi.m

% Chiede all'utente numeri positivi e li ristampa
% finché l'utente inserisce numeri positivi
%
% Conta i numeri positivi inseriti dall'utente

counter = 0;
inputnum = input('Inserisci un numero positivo: ');
while inputnum >= 0
    fprintf('Hai inserito %d.\n\n', inputnum)
    counter = counter + 1;
    inputnum = input('Inserisci un numero positivo: ');
end
fprintf('Grazie, hai inserito %d numeri positivi.\n', counter)

Lo script inizializza la variabile counter a 0. Poi, nel ciclo while, ogni volta che l'utente inserisce correttamente un numero, lo script incrementa la variabile counter.

Alla fine dello script, stampa il numero di valori positivi che sono stati inseriti.

Ecco un possibile esempio di esecuzione:

>> conta_numeri_positivi
Inserisci un numero positivo: 4
Hai inserito 4.

Inserisci un numero positivo: 11
Hai inserito 11.

Inserisci un numero positivo: -4
Grazie, hai inserito 2 numeri positivi.

Esercizio: Calcolare la media dei numeri negativi

Proviamo a risolvere un problema più complesso.

Realizziamo uno script chiamato media_numeri_negativi che ripeta il processo di chiedere all'utente numeri negativi, finché l'utente non inserisce uno zero o un numero positivo, come appena mostrato.

Invece di ristamparli, tuttavia, lo script stamperà la media (soltanto dei numeri negativi). Se non vengono inseriti numeri negativi, lo script stamperà un messaggio di errore anziché la media.

Applichiamo le tecniche viste fino a questo punto. Ecco alcuni esempi di esecuzione:

>> media_numeri_negativi
Inserisci un numero negativo: 5
Nessun numero negativo inserito.
>> media_numeri_negativi
Inserisci un numero negativo: -8
Inserisci un numero negativo: -3
Inserisci un numero negativo: -4
Inserisci un numero negativo: 6
La media dei numeri negativi è -5.00

Proviamo a scrivere lo script media_numeri_negativi per risolvere questo problema.

% media_numeri_negativi.m

% Chiede all'utente numeri negativi e calcola la media
% dei numeri negativi inseriti

counter = 0;
sum = 0;
inputnum = input('Inserisci un numero negativo: ');
while inputnum < 0
    sum = sum + inputnum;
    counter = counter + 1;
    inputnum = input('Inserisci un numero negativo: ');
end

if counter == 0
    fprintf('Nessun numero negativo inserito.\n')
else
    fprintf('La media dei numeri negativi è %.2f\n', sum/counter)
end

Controllo degli errori sull'input dell'utente in un ciclo while

Nella maggior parte delle applicazioni, quando all'utente viene chiesto di inserire un valore, esiste un intervallo di valori valido.

Se l'utente inserisce un valore errato, invece di proseguire con un valore incorretto o di stampare un messaggio di errore una sola volta, il programma dovrebbe ripetere la richiesta. Il programma deve continuare a chiedere all'utente di inserire il valore, leggerlo e controllarlo finché l'utente non inserisce un valore che rientra nell'intervallo corretto.

Questa è una tipica applicazione di un ciclo condizionale: si itera finché l'utente non inserisce un valore valido. Questo procedimento si chiama controllo degli errori (error-checking).

Ad esempio, lo script seguente chiede all'utente di inserire un numero positivo e ripete un messaggio di errore, riproponendo il prompt finché l'utente non inserisce finalmente un numero positivo.

% leggi_un_numero.m

% Si ripete finché l'utente non inserisce un numero positivo

inputnum = input('Inserisci un numero positivo: ');
while inputnum < 0
    inputnum = input('Non Valido! Inserisci un numero positivo: ');
end
fprintf('Grazie, hai inserito %.1f \n', inputnum)

Ecco un esempio di esecuzione di questo script:

>> leggi_un_numero
Inserisci un numero positivo: -5
Non Valido! Inserisci un numero positivo: -2.2
Non Valido! Inserisci un numero positivo: c
Error using input
Unrecognized function or variable 'c'.

Error in leggi_un_numero (line 5)
inputnum = input('Non Valido! Inserisci un numero positivo: ');

Non Valido! Inserisci un numero positivo: 44
Grazie, hai inserito 44.0

Si noti che MATLAB intercetta da solo l'input di caratteri e stampa un messaggio di errore, ripetendo il prompt quando l'utente digita c.

Domanda: come potremmo variare l'esempio precedente in modo che lo script chieda all'utente di inserire numeri positivi n volte, dove n è un intero definito a 3?

Risposta: a ogni inserimento di un valore, lo script effettua il controllo in un ciclo while e continua a segnalare che è invalido finché non viene inserito un numero positivo. Includendo il controllo degli errori in un ciclo for che viene ripetuto n volte, l'utente è obbligato a inserire un totale di 3 numeri positivi, come illustrato di seguito.

leggi_n_numeri.m

% Ciclo finché l'utente non inserisce n numeri positivi
n = 3;
fprintf('Inserire %d numeri positivi\n\n',n)
for i = 1:n
    inputnum = input('Inserisci un numero positivo: ');
    while inputnum < 0
        inputnum = input('Non Valido! Inserisci un numero positivo: ');
    end
    fprintf('Grazie, hai inserito %.1f \n',inputnum)
end

Un esempio di esecuzione:

>> leggi_n_numeri
Inserire 3 numeri positivi

Inserisci un numero positivo: 5.2
Grazie, hai inserito 5.2
Inserisci un numero positivo: 6
Grazie, hai inserito 6.0
Inserisci un numero positivo: -7.7
Non Valido! Inserisci un numero positivo: 5
Grazie, hai inserito 5.0

Controllo degli errori per interi

Poiché MATLAB usa il tipo double di default per tutti i valori, per verificare che l'utente abbia inserito un intero, il programma deve convertire il valore inserito in un tipo intero (ad esempio, int32), e poi controllare se questo corrisponde al valore originale. I seguenti esempi illustrano il concetto.

Se il valore della variabile num è un numero reale, convertirlo in int32 lo arrotonderà, e quindi il risultato non sarà uguale al valore originale:

>> num = 3.3;
>> inum = int32(num)
inum =
     3
>> num == inum
ans =
     0

Se invece il valore della variabile num è già un intero, convertirlo in int32 non cambierà il valore:

>> num = 4;
>> inum = int32(num)
inum =
     4
>> num == inum
ans =
     1

Lo script seguente usa questa idea per il controllo degli errori sui dati di tipo intero: itera finché l'utente non inserisce correttamente un intero.

leggi_intero.m

% Controllo degli errori finché l'utente non inserisce un intero

inputnum = input('Inserisci un numero intero: ');
num2 = int32(inputnum);
while num2 ~= inputnum
    inputnum = input('Non Valido! Inserisci un numero intero: ');
    num2 = int32(inputnum);
end
fprintf('Grazie, hai inserito %d\n', inputnum)

Esempi di esecuzione di questo script:

>> leggi_intero
Inserisci un numero intero: 9.5
Non Valido! Inserisci un numero intero: 3.6
Non Valido! Inserisci un numero intero: -11
Grazie, hai inserito -11
>> leggi_intero
Inserisci un numero intero: 5
Grazie, hai inserito 5

Nota: si presuppone che l'utente inserisca qualcosa. Nel caso in cui si voglia essere certi di questo, si può utilizzare la funzione isempty.

Mettendo insieme queste idee, lo script seguente itera finché l'utente non inserisce correttamente un intero positivo. Ci sono due parti nella condizione, poiché il valore deve essere positivo e deve essere un intero.

leggi_intero_positivo.m

% Controllo degli errori finché l'utente non inserisce un intero positivo

inputnum = input('Inserisci un numero intero positivo: ');
num2 = int32(inputnum);
while num2 ~= inputnum || num2 < 0
    inputnum = input('Non Valido! Inserisci un numero intero positivo: ');
    num2 = int32(inputnum);
end
fprintf('Grazie, hai inserito %d \n', inputnum)

Esempio di esecuzione:

>> leggi_intero_positivo
Inserisci un numero intero positivo: 5.5
Non Valido! Inserisci un numero intero positivo: -4
Non Valido! Inserisci un numero intero positivo: 11
Grazie, hai inserito 11

Esercizio: Leggere n numeri interi positivi

Modifichiamo lo script leggi_intero_positivo in modo che legga n interi positivi, invece di uno soltanto.

% leggi_n_interi_positivi.m

% Legge n numeri interi positivi

n = 3;
risultato = zeros(1,n);
fprintf('Inserire %d numeri interi positivi\n\n',n)

for i = 1:n
    inputnum = input('Inserisci un numero intero positivo: ');
    num2 = int32(inputnum);
    while num2 ~= inputnum || num2 < 0
        inputnum = input('Non Valido! Inserisci un numero intero positivo: ');
        num2 = int32(inputnum);
    end
    fprintf('Grazie, hai inserito %d \n', inputnum)
    risultato(i) = inputnum;
end

Esempio di esecuzione:

>> leggi_n_interi_positivi
Inserire 3 numeri interi positivi

Inserisci un numero intero positivo: 5
Grazie, hai inserito 5
Inserisci un numero intero positivo: -3
Non Valido! Inserisci un numero intero positivo: 7
Grazie, hai inserito 7
Inserisci un numero intero positivo: 4.5
Non Valido! Inserisci un numero intero positivo: 9
Grazie, hai inserito 9

Al termine dello script, il vettore risultato conterrà i numeri interi positivi inseriti dall'utente.

In Sintesi

In questa lezione abbiamo studiato che:

  • Il ciclo while in MATLAB viene utilizzato per ripetere un'azione finché una condizione è vera.
  • La condizione del ciclo while può essere una qualsiasi espressione booleana.
  • È possibile utilizzare condizioni multiple con gli operatori logici && (and) e || (or).
  • Il ciclo while può essere utilizzato per leggere dati da un file fino a un valore sentinella.
  • È possibile utilizzare il ciclo while per elaborare input dell'utente finché non viene inserito un valore valido.
  • MATLAB consente di estendere un vettore dinamicamente durante l'esecuzione di un ciclo while.
  • È possibile contare il numero di iterazioni di un ciclo while utilizzando una variabile contatore.
  • Il controllo degli errori può essere implementato in un ciclo while per garantire che l'utente inserisca valori validi.
  • MATLAB fornisce funzioni per verificare se un valore è un intero e per gestire input non numerici.
  • Il ciclo while può essere combinato con altri cicli, come il ciclo for, per iterare un numero specifico di volte.