Cicli For Innestati in MATLAB
In questa lezione esploreremo i cicli for
innestati in MATLAB, una tecnica fondamentale per gestire operazioni ripetitive su matrici e array multidimensionali.
Impareremo a creare cicli for
all'interno di altri cicli for
, comprendendo come iterare su righe e colonne per eseguire calcoli complessi. Scopriremo anche come combinare cicli for
con istruzioni if
per manipolare e analizzare dati in modo efficiente.
Cicli for
innestati in MATLAB
Il corpo di un ciclo for
in MATLAB può consistere in qualunque istruzione (o insieme di istruzioni) valida.
Quando il corpo di un ciclo for
è a sua volta un ciclo for
, si parla di ciclo for
innestato o ciclo for
annidato.
La forma generale di un ciclo for
innestato è la seguente:
for varloop1 = intervallo1 % <-- ciclo esterno
% corpo1, che include il ciclo interno
for varloop2 = intervallo2 % <-- ciclo interno
corpo2
end
end
Il primo ciclo for
è chiamato ciclo esterno; il secondo for
è il ciclo interno. L'azione del ciclo esterno consiste (in parte; potrebbero esserci altre istruzioni) nell'intero ciclo interno.
Come esempio, mostriamo un ciclo annidato in uno script che stamperà un rettangolo di asterischi (*
). Le variabili nello script specificano quante righe e quante colonne stampare. Per esempio, se la variabile righe
è impostata a 3 e colonne
a 5, verrà stampato un rettangolo di dimensioni 3×5.
Poiché la stampa su diverse righe avviene tramite il carattere di newline, l'algoritmo di base è il seguente:
Per ogni riga di output:
- Stampare il numero richiesto di asterischi
- Spostare il cursore sulla riga successiva (stampando
\n
)
% stampa_rettangolo.m
% Stampa un rettangolo di asterischi
% Il numero di righe e colonne è specificato
% dalle variabili righe e colonne
righe = 3;
colonne = 5;
% ciclo sulle righe
for i = 1:righe
% per ogni riga, un ciclo per stampare
% gli asterischi e poi andare a capo
for j = 1:colonne
fprintf('*')
end
fprintf('\n')
end
Esempio di esecuzione e relativo output:
>> stampa_rettangolo
*****
*****
*****
La variabile righe
specifica quante righe stampare, mentre la variabile colonne
specifica quanti asterischi stampare in ogni riga.
Sono presenti due variabili di ciclo: i
itera sulle righe e j
itera sulle colonne. Poiché il numero di righe (righe
) e di colonne (colonne
) è noto, vengono utilizzati due cicli for
. Uno serve a iterare sulle righe e l'altro a stampare il numero richiesto di asterischi per ogni riga.
I valori delle variabili di ciclo non vengono effettivamente usati nelle istruzioni, ma servono semplicemente a determinare quante volte ripetere l'azione.
Il primo ciclo for
stabilisce che l'azione si ripeterà un numero di volte pari a righe
. L'azione di questo ciclo è di stampare gli asterischi e poi il carattere di newline. In particolare, tale azione consiste in un ulteriore ciclo che stampa colonne
asterischi su una riga; solo dopo questi colonne
asterischi viene stampato il carattere di ritorno a capo.
In questo caso, il ciclo esterno itera sulle righe, mentre il ciclo interno itera sulle colonne. Il ciclo esterno deve riguardare le righe poiché lo script deve stampare un certo numero di righe di output. Per ogni riga, è necessario un ciclo dedicato a stampare il numero richiesto di asterischi, e questo è il ciclo interno.
Durante l'esecuzione, per prima cosa la variabile del ciclo esterno i
è inizializzata a 1, e poi si passa al suo corpo.
Il corpo include l'esecuzione del ciclo interno e in seguito la stampa del carattere di newline. Mentre i
=1, la variabile del ciclo interno j
itera su tutti i suoi valori. Avendo colonne=5
, il ciclo interno stamperà un singolo asterisco per cinque volte. In seguito verrà stampato il carattere di newline, quindi i
sarà incrementato a 2 e così via, finché l'azione del ciclo esterno non sarà stata eseguita righe
volte.
Osserviamo che l'azione del ciclo esterno comprende due istruzioni (il ciclo interno for e la chiamata fprintf('\n')
), mentre l'azione del ciclo interno è una singola chiamata fprintf('*')
.
Si noti bene che se avessimo usato un'unica istruzione nel ciclo interno del tipo fprintf('*\n')
, avremmo ottenuto una singola colonna di 15 asterischi (nel caso 3×5), anziché un rettangolo di 3 righe e 5 colonne.
Proviamo a modificate lo script stampa_rettangolo
per stampare un triangolo di asterischi anziché un rettangolo. Ad esempio, se righe
è impostato a 3, lo script dovrebbe stampare:
*
**
***
In questo caso, il numero di asterischi da stampare in ogni riga è uguale al numero della riga. Il ciclo interno non deve iterare fino a colonne
, ma fino al valore della variabile di ciclo del ciclo esterno; quindi la variabile colonne
non serve.
stampa_triangolo.m
% Stampa un triangolo di asterischi
% Il numero di righe sarà specificato dalla variabile "righe"
righe = 3;
for i = 1:righe
% il ciclo interno si ferma a i
for j = 1:i
fprintf('*')
end
fprintf('\n')
end
Nei precedenti esempi, le variabili di ciclo servivano esclusivamente a specificare quante volte ripetere l'azione. Nel prossimo esempio, invece, verranno stampati effettivamente i valori delle variabili di loop.
stampa_variabili_loop.m
% Mostra i valori delle variabili di loop
for i = 1:3
for j = 1:2
fprintf('i=%d, j=%d\n', i, j)
end
fprintf('\n')
end
Eseguendo questo script, si otterrà la stampa dei valori di i
e j
su una riga ogni volta che si esegue l'azione del ciclo interno. L'azione del ciclo esterno consiste nell'eseguire il ciclo interno e poi stampare una riga vuota (\n
), separando così le iterazioni del ciclo esterno:
>> stampa_variabili_loop
i=1, j=1
i=1, j=2
i=2, j=1
i=2, j=2
i=3, j=1
i=3, j=2
Ora, invece di limitarci a stampare i valori delle variabili di loop, possiamo usarli per generare una tabella di moltiplicazione, moltiplicando i valori delle variabili dei due cicli.
Lo script seguente, chiamato tabellamolt
, calcola e restituisce una matrice che rappresenta una tabella di moltiplicazione. Vengono passati due argomenti alla funzione, che indicano il numero di righe e di colonne della matrice desiderata.
tabellamolt.m
function matout = tabellamolt(righe, colonne)
% tabellamolt restituisce una matrice che rappresenta
% una tabella di moltiplicazione
% Formato: tabellamolt(nRighe, nColonne)
% Preallocazione della matrice
matout = zeros(righe, colonne);
for i = 1:righe
for j = 1:colonne
matout(i, j) = i * j;
end
end
end
Chiamando la funzione con:
>> tabellamolt(3,5)
si ottiene una matrice con 3 righe e 5 colonne:
ans =
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
Si tratta di una funzione che restituisce una matrice.
Prealloca la matrice a zeri, quindi sostituisce ogni elemento con il prodotto i * j
. Poiché si conoscono il numero di righe e di colonne, si usano cicli for: quello esterno per le righe e quello interno per le colonne. L'azione del ciclo annidato calcola i * j
per tutti i valori di i
e j
. Come avviene con i vettori, è di nuovo importante notare che le variabili di loop vengono utilizzate come indici della matrice.
- Quando
i=1
,j
itera da 1 a 5, calcolando1*1
,1*2
,1*3
,1*4
,1*5
, che diventano gli elementi della prima riga. - Quando
i=2
, gli elementi della seconda riga sono2*1
,2*2
,2*3
,2*4
,2*5
. - Quando
i=3
, gli elementi della terza riga sono3*1
,3*2
,3*3
,3*4
,3*5
.
Questa funzione potrebbe essere utilizzata in uno script che chiede all'utente il numero di righe e di colonne, richiama la funzione per generare la tabella di moltiplicazione e infine scrive la matrice risultante in un file:
creatabellamolt.m
% Richiede all'utente il numero di righe e colonne
% e crea una tabella di moltiplicazione da salvare
% nel file "miatabellamolt.dat"
num_righe = input('Inserire il numero di righe: ');
num_colonne = input('Inserire il numero di colonne: ');
moltmatrice = tabellamolt(num_righe, num_colonne);
save miatabellamolt.dat moltmatrice -ascii
Un esempio di esecuzione:
>> creatabellamolt
Inserire il numero di righe: 6
Inserire il numero di colonne: 4
>> load miatabellamolt.dat
>> miatabellamolt
miatabellamolt =
1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
5 10 15 20
6 12 18 24
Esercizio Pratico
Per chiarire il concetto di cicli innestati, proviamo a risolvere i seguenti esercizi.
Per ognuno dei seguenti blocchi di codice (che sono separati), determinare cosa verrà stampato.
mat = [7 11 3; 3 5];
[r, c] = size(mat);
for i = 1:r
fprintf('La somma è %d\n', sum(mat(i,:)))
end
for i = 1:2
fprintf('%d: ', i)
for j = 1:4
fprintf('%d ', j)
end
fprintf('\n')
end
Nel primo caso, la matrice mat
è definita come mat = [7 11 3; 3 5]
, quindi essa sarà una matrice 2×3:
7 11 3
3 5 0
Il ciclo esterno itera sulle righe della matrice, quindi i
varrà 1 e 2. Per i=1
, sum(mat(1,:))
calcola la somma degli elementi della prima riga, cioè 7+11+3=21
. Per i=2
, sum(mat(2,:))
calcola la somma degli elementi della seconda riga, cioè 3+5+0=8
.
Quindi l'output sarà:
La somma è 21
La somma è 8
Nel secondo caso, il ciclo esterno itera su i=1
e i=2
. Per ogni valore di i
, il ciclo interno itera su j=1
, j=2
, j=3
, j=4
. Quindi, per i=1
, verrà stampato 1 2 3 4
, e per i=2
, verrà stampato 1 2 3 4
.
Quindi l'output sarà:
1: 1 2 3 4
2: 1 2 3 4
Combinare cicli for
annidati e istruzioni if
Le istruzioni all'interno di un ciclo annidato possono essere una qualunque istruzione valida, incluse le istruzioni di selezione. Ad esempio, è possibile usare un'istruzione if
o if
-else
come azione (o parte dell'azione) in un ciclo.
Come esempio, si supponga di avere un file chiamato datavalori.dat
contenente i risultati di un esperimento.
Alcuni valori sono stati registrati in modo errato: tutti i numeri dovrebbero essere positivi, ma non lo sono. Lo script seguente legge da questo file, memorizza i dati in una matrice e stampa la somma solo dei numeri positivi per ogni riga.
Si assume che il file contenga numeri interi; non si assume però quante righe ci siano nel file né quanti numeri per riga (anche se si suppone che su ogni riga ci sia lo stesso numero di interi).
sumsolopositivi.m
% Somma solo i numeri positivi da un file
% Legge il file in una matrice e poi calcola e stampa
% la somma solo dei numeri positivi da ogni riga
load datavalori.dat
[r, c] = size(datavalori);
for riga = 1:r
sommaCorrente = 0;
for colonna = 1:c
if datavalori(riga,colonna) >= 0
sommaCorrente = sommaCorrente + datavalori(riga,colonna);
end
end
fprintf('La somma per la riga %d è %d\n', riga, sommaCorrente)
end
Per esempio, se il file datavalori.dat
contenesse:
33 -11 2
4 5 9
22 5 -7
2 11 3
l'output del programma sarebbe:
>> sumsolopositivi
La somma per la riga 1 è 35
La somma per la riga 2 è 18
La somma per la riga 3 è 27
La somma per la riga 4 è 16
In questo caso, il file viene caricato e i dati sono memorizzati in una variabile di tipo matrice.
Lo script trova le dimensioni di tale matrice e poi itera su tutti gli elementi usando un ciclo annidato: il ciclo esterno scorre le righe, mentre quello interno scorre le colonne. Questo è importante perché, volendo eseguire un'azione per ogni riga, il ciclo esterno dev'essere quello che itera sulle righe.
Per ogni elemento, un'istruzione if
determina se l'elemento è positivo o no. Solo i valori positivi vengono aggiunti alla somma di quella riga. Poiché la somma si deve calcolare per ciascuna riga, la variabile sommaCorrente viene inizializzata a 0 all'inizio di ogni iterazione del ciclo esterno.
Domanda: Farebbe differenza invertire l'ordine dei cicli in questo esempio, in modo che il ciclo esterno iteri sulle colonne e quello interno sulle righe?
Risposta: Sì, perché vogliamo calcolare una somma per ogni riga. Di conseguenza, il ciclo esterno deve riguardare le righe.
Domanda: Cosa sarebbe necessario cambiare per calcolare e stampare la somma dei soli numeri positivi per ciascuna colonna invece che per ogni riga?
Risposta: Basterebbe invertire i due cicli e sostituire la frase con "La somma per la colonna..."
. Tutto il resto rimarrebbe invariato: gli elementi della matrice si referenziano comunque come datavalori(riga,colonna)
, perché l'indice di riga si indica sempre per primo.
Esercizio Pratico
Scrivere una funzione mioMatMin
che trovi il valore minimo in ogni colonna di una matrice passata come argomento e restituisca un vettore di tali minimi. Si utilizzi il metodo di programmazione basato su cicli. Ecco un esempio di chiamata:
>> mat = randi([1 20], 3, 4)
mat =
15 19 17 5
6 14 13 13
9 5 3 13
>> mioMatMin(mat)
ans =
6 5 3 5
Una possibile soluzione è la seguente:
mioMatMin.m
function minimi = mioMatMin(matrice)
% Restituisce il valore minimo di ogni colonna di una matrice
[righe, colonne] = size(matrice);
minimi = zeros(1, colonne);
for col = 1:colonne
minimoCorrente = matrice(1, col);
for riga = 2:righe
if matrice(riga, col) < minimoCorrente
minimoCorrente = matrice(riga, col);
end
end
minimi(col) = minimoCorrente;
end
end
Domanda: La funzione mioMatMin
dell'esempio funzionerebbe anche per un vettore come argomento?
Risposta: Sì. Un vettore non è altro che un sottoinsieme di una matrice. In questo caso, una delle due dimensioni (righe o colonne) comporterebbe l'esecuzione di un solo ciclo.
In Sintesi
In questa lezione abbiamo approfondito il concetto di cicli for
innestati, ovvero cicli for
all'interno di altri cicli for
.
I concetti chiave che abbiamo imparato sono:
- Un ciclo
for
innestato è un ciclofor
all'interno di un altro ciclofor
. - Il ciclo esterno è quello che itera sulle righe, mentre il ciclo interno itera sulle colonne o viceversa.
- Le variabili di loop vengono utilizzate come indici per accedere agli elementi di una matrice.
- Le istruzioni all'interno di un ciclo annidato possono essere qualsiasi istruzione valida, inclusi cicli
for
e istruzioni di selezione comeif
eif-else
.
Abbiamo anche visto come combinare cicli for
annidati con istruzioni if
per eseguire azioni complesse su matrici, come calcolare la somma dei soli numeri positivi per ogni riga di una matrice.
Infine, abbiamo risolto un esercizio pratico che richiedeva di scrivere una funzione che trovasse il valore minimo in ogni colonna di una matrice.
Nella prossima lezione, invece, studieremo un altro tipo di ciclo: il ciclo while
.