In queste settimane, nelle terze degli istituti tecnici a indirizzo Informatica, il programma è arrivato agli array: prima quelli a una dimensione, poi le matrici. La verifica, in genere pratica al PC più uno scritto, cade fra sei e otto settimane. Il professore quasi sempre la costruisce attorno a un esercizio con una tabella di numeri: riempirla, stamparla, sommare righe e colonne, trovare un massimo. Questo post serve a chi ha appena incontrato il doppio indice m[i][j] e vuole arrivare alla verifica senza confondere righe e colonne.
Se ti manca la base sui cicli e sugli array monodimensionali, parti da cicli annidati e array in terza informatica: qui diamo per scontato che un for semplice non ti faccia paura.
Che cos’è una matrice, in pratica
Un array bidimensionale è una tabella: righe e colonne. In C lo dichiari con due dimensioni, prima il numero di righe, poi quello di colonne:
int m[3][4];
Significa 3 righe e 4 colonne, quindi 12 elementi, tutti dello stesso tipo. Si accede a un elemento con due indici: m[i][j] è l’elemento della riga i e della colonna j. Gli indici partono da 0, quindi la prima riga è la numero 0 e l’ultima è la numero 2.
Puoi inizializzarla già in fase di dichiarazione, una riga per volta fra graffe:
int m[3][4] = {
{4, 7, 1, 9},
{2, 8, 6, 3},
{5, 0, 10, 12}
};
Su questa matrice, m[0][0] vale 4, m[1][2] vale 6 e m[2][3] vale 12. Ci lavoreremo per tutto il post, quindi vale la pena copiarla su un foglio come griglia con le righe numerate 0, 1, 2 e le colonne numerate 0, 1, 2, 3.
Una cosa che a volte il docente chiede nello scritto: in memoria gli elementi sono contigui e, in C, ordinati per riga. Prima tutta la riga 0, poi la riga 1, poi la 2. Non serve per scrivere i cicli, ma spiega perché, quando passi una matrice a una funzione, il compilatore vuole conoscere il numero di colonne.
Il ciclo annidato: la regola che regge tutto
Per visitare tutti gli elementi servono due cicli, uno dentro l’altro. Il ciclo esterno scorre le righe, quello interno le colonne:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d", m[i][j]);
}
printf("\n");
}
L’output è:
4 7 1 9
2 8 6 3
5 0 10 12
Il ciclo interno gira per intero a ogni passo di quello esterno: in totale il corpo viene eseguito 3 × 4 = 12 volte. Il printf("\n") sta dopo il ciclo interno ma dentro quello esterno, ed è la riga che manda a capo alla fine di ogni riga della tabella. Spostarlo di una graffa, dentro il ciclo interno o fuori da entrambi, è uno degli errori più comuni: la matrice esce tutta su una riga o con un a capo dopo ogni numero.
Il consiglio che do sempre: prima di scrivere il ciclo, scrivi sul foglio chi sono i e j. Io uso sempre i per le righe e j per le colonne, e non cambio per tutto l’esercizio.
Python: la stessa idea, sintassi diversa
In Python una matrice è una lista di liste. Ogni lista interna è una riga:
m = [
[4, 7, 1, 9],
[2, 8, 6, 3],
[5, 0, 10, 12],
]
for i in range(3):
for j in range(4):
print(f"{m[i][j]:3d}", end="")
print()
L’output è identico a quello del programma C. Gli indici funzionano allo stesso modo: m[1][2] vale 6. Le differenze che ti interessano sono due. Le dimensioni non si dichiarano, quindi se ti servono le ricavi con len(m) per le righe e len(m[0]) per le colonne. E si può ciclare direttamente sugli elementi, senza indici, quando non ti serve sapere la posizione:
for riga in m:
for x in riga:
print(f"{x:3d}", end="")
print()
Gli indici restano necessari quando la posizione conta, come nella diagonale o nella trasposta.
Somma per righe e per colonne
Somma di ogni riga: per ogni i azzeri un accumulatore, poi scorri le colonne.
for (int i = 0; i < 3; i++) {
int somma = 0;
for (int j = 0; j < 4; j++) {
somma += m[i][j];
}
printf("riga %d: %d\n", i, somma);
}
Con la nostra matrice stampa riga 0: 21, riga 1: 19, riga 2: 27. Controllo a mano la prima: 4 + 7 + 1 + 9 = 21.
Per le colonne si invertono i ruoli: il ciclo esterno scorre j, quello interno scorre i, e l’indice che cambia nel ciclo interno è quello della riga.
for (int j = 0; j < 4; j++) {
int somma = 0;
for (int i = 0; i < 3; i++) {
somma += m[i][j];
}
printf("colonna %d: %d\n", j, somma);
}
Il risultato: colonna 0: 11, colonna 1: 15, colonna 2: 17, colonna 3: 24. Verifica: 4 + 2 + 5 = 11 per la prima colonna. Come controllo finale, la somma delle somme di riga (21 + 19 + 27 = 67) deve coincidere con quella delle somme di colonna (11 + 15 + 17 + 24 = 67). In verifica è un controllo che costa trenta secondi.
La versione Python è breve:
for i in range(len(m)):
print(f"riga {i}: {sum(m[i])}")
for j in range(len(m[0])):
somma = 0
for i in range(len(m)):
somma += m[i][j]
print(f"colonna {j}: {somma}")
Diagonale principale e trasposta
La diagonale principale esiste in una matrice quadrata, cioè con tante righe quante colonne: sono gli elementi con indice di riga uguale a quello di colonna, m[0][0], m[1][1], m[2][2]. Qui basta un ciclo solo, perché i due indici sono uguali:
int q[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int diag = 0;
for (int i = 0; i < 3; i++) {
diag += q[i][i];
}
printf("diagonale: %d\n", diag);
L’output è diagonale: 15, perché 1 + 5 + 9 = 15. Un errore tipico è usare due cicli annidati con un if (i == j) dentro: funziona, ma fa 9 passi invece di 3 e il professore lo nota.
La trasposta scambia righe e colonne: l’elemento in posizione [i][j] finisce in [j][i]. Se la matrice originale è 2 righe per 3 colonne, la trasposta è 3 righe per 2 colonne, quindi serve una matrice nuova con le dimensioni scambiate:
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
int b[3][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
b[j][i] = a[i][j];
}
}
Stampando b riga per riga si ottiene:
1 4
2 5
3 6
In Python la stessa operazione si può scrivere con [list(r) for r in zip(*a)], ma se non l’hai vista a lezione usa i due cicli: in verifica conta far vedere che hai capito lo scambio degli indici.
Ricerca del massimo, con la posizione
Per trovare il massimo parti dal primo elemento, non da zero: se tutti i numeri fossero negativi, partire da 0 darebbe un risultato sbagliato. Poi scorri e aggiorni, ricordando anche dove l’hai trovato:
int max = m[0][0];
int mi = 0, mj = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (m[i][j] > max) {
max = m[i][j];
mi = i;
mj = j;
}
}
}
printf("massimo %d in [%d][%d]\n", max, mi, mj);
Con la nostra matrice stampa massimo 12 in [2][3]. In Python il codice è lo stesso con range. A questo punto è utile una funzione: in C la matrice passata come parametro non viene copiata, e va indicato il numero di colonne, ad esempio void stampa(int m[][4], int righe). Il numero di righe lo passi a parte, quello delle colonne deve comparire nel tipo. Se hai dubbi su cosa significhi questo passaggio, c’è il post su passaggio di parametri per valore e per riferimento.
Gli errori che fanno perdere punti
- Indici scambiati. Scrivere
m[j][i]al posto dim[i][j]compila e gira. Se la matrice è quadrata il risultato è solo trasposto e non te ne accorgi subito; se è rettangolare ti ritrovi fuori dai limiti. - Fuori dai limiti. Su
int m[3][4]l’indice di riga valido va da 0 a 2 e quello di colonna da 0 a 3. Scriverei <= 3nel ciclo delle righe fa leggerem[3][...], che non esiste. In C non c’è nessun controllo: il comportamento è indefinito, a volte il programma sembra funzionare, a volte esce un numero strano. In Python, invece, ottieni subito unIndexError. - Accumulatore non azzerato. Nella somma per righe,
int somma = 0;deve stare dentro il ciclo esterno. Messo fuori, la somma della seconda riga include anche la prima. - Dimensioni scritte a mano ovunque. Se dichiari
#define RIGHE 3e#define COLONNE 4, un solo cambiamento aggiorna tutto il programma. Sparso fra dichiarazione e cicli, il numero 4 ti tradisce al primo cambio di traccia. - Solo Python: creare la matrice con
[[0] * 3] * 3. Sembra giusto, ma replica tre volte la stessa lista. Se poi scrivig[0][0] = 1, ottieni[[1, 0, 0], [1, 0, 0], [1, 0, 0]]. Il modo corretto è[[0] * 3 for _ in range(3)], che con la stessa assegnazione dà[[1, 0, 0], [0, 0, 0], [0, 0, 0]].
Esercizi svolti
Esercizio 1. Data la matrice m di 3 righe e 4 colonne vista sopra, calcola la somma degli elementi sulla riga 1 e la media di tutti gli elementi.
Riga 1 è {2, 8, 6, 3}: 2 + 8 + 6 + 3 = 19. La somma di tutti gli elementi è 21 + 19 + 27 = 67 e gli elementi sono 12, quindi la media è 67 / 12, circa 5,58. In C, ricordati che 67 / 12 fra interi dà 5: serve (double) somma / 12.
Esercizio 2. Nella matrice quadrata q = {{1,2,3},{4,5,6},{7,8,9}}, calcola la somma della diagonale secondaria, cioè gli elementi q[0][2], q[1][1], q[2][0].
Qui la somma degli indici è sempre 2, cioè i + j = N - 1 con N = 3. Quindi basta un ciclo con q[i][2 - i]: 3 + 5 + 7 = 15. Qui coincide con la diagonale principale (anch’essa 15) solo perché i numeri crescono regolarmente da 1 a 9: su una matrice qualsiasi i due valori sono diversi.
Esercizio 3. Scrivi il frammento che conta quanti elementi pari ci sono in m e stampa il risultato. Aspettati un numero e verificalo a mano.
int pari = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (m[i][j] % 2 == 0) {
pari++;
}
}
}
printf("pari: %d\n", pari);
Riga 0: 4 è pari, 7 no, 1 no, 9 no, quindi 1. Riga 1: 2, 8, 6 sono pari, 3 no, quindi 3. Riga 2: 0, 10, 12 sono pari, 5 no, quindi 3. Totale 7: l’output è pari: 7. Nota che 0 conta come pari.
Come prepararti nelle prossime settimane
Su informatica, per ora, non c’è ancora una simulazione dedicata: la prova pratica si allena scrivendo codice. Il mio metodo è fare un esercizio al giorno, prima a mano sul foglio con la griglia e i valori di i e j, poi al computer, e confrontare l’output con quello atteso. Ogni passaggio visto qui, somme, diagonale, trasposta, massimo, lo puoi riscrivere in C e in Python senza guardare. Altro materiale sulla materia è nella pagina informatica. Per le stringhe e i file vedi stringhe e file in Python, e per capire che cosa c’è sotto gli indici in C, i puntatori spiegati bene.
Se vuoi capire dove sei, scrivimi dalla pagina contatti e facciamo una diagnosi gratuita: guardiamo insieme un tuo esercizio e capiamo se l’intoppo è negli indici, nei cicli o nella lettura della traccia.