Le matrici: alcune considerazioni ...

 

Eccoci alle prese con le Matrici o Array.

Questa volta l'argomento che vado a trattare risulta particolarmente lungo ed articolato.

Si può pensare alla matrice come ad uno scaffale di un supermercato dove ci sono, ordinati per tipo, vari articoli (variabili). In ogni scaffale tuttavia c'è un solo tipo di articoli: c'è lo scaffale del latte, quello del tonno, quello del pane e tutti questi articoli sono suddivisi in uno o più ripiani.

Il concetto per definire una matrice è quello di una estensione del concetto di Variabile. Infatti in una matrice noi troveremo tutta una serie di dati utilizzando una sola variabile (con un indice che va da un valore minimo ad un valore massimo).

Ma iniziamo prima con un minimo di teoria a partire dalle variabili in genere.

Per l'assegnazione dei loro nomi, oltre ad accettare le norme generali io ho anche delle piccole manie che tuttavia molte volte mi hanno salvato da un ricovero coatto in psichiatria. Qui di seguito riferisco le norme generali (quelle che esige VBA) e le mie (queste seconde le troveremo evidenziate in grassetto).

Anche se non è obbligatorio, la dichiarazione delle variabili tramite l'istruzione Dim consente di ridurre il numero di errori dovuti ad errori di digitazione o confusione (io a questo proposito ho sempre attivo la "Dichiarazione di variabili obbligatoria" in Strumenti ----> Opzioni --> Editor che nei nuovi moduli che intendo usare inserisce l'istruzione "Option Explicit").

Infatti potrebbe succedere una cosa del genere come illustrato nel seguente esempio:

costounitario = 55
Quantita = 100
Totale = costountario * Quantita

La variabile scritta nel terzo rigo ed in grassetto è sbagliata, ma in questo caso nemmeno il VBA può rendersi conto se è una nuova variabile non dichiarata ed ancora non valorizzata o è una variabile già esistente non scritta correttamente per cui le assegna senz'altro un valore errato causando tuttavia dei risultati assolutamente sbagliati.

Se noi avessimo provveduto a mettere una bella: Dim CostoUnitario, nel corso della stesura di questa frazione di codice noi avremmo notato subito che dopo aver scritto costounitario, uscendo dal rigo, si sarebbe tramutato in CostoUnitario facendoci capire di aver scritto correttamente, mentre costountario sarebbe rimasto invariato facendoci capire di aver sbagliato qualche cosa nella digitazione.

Io suggerirei per chi è alle prime armi o particolarmente distratto, come il sottoscritto, di inserire all'inizio di ogni modulo questa istruzione:

Option Explicit

o addirittura impostare in modo permanente l'editor VBA in modo che detta istruzione venga inserita automaticamente in tutti i nuovi moduli agendo nel seguente modo:

nell'Editor VBA fare le seguenti scelte:

Strumenti / Opzioni - Editor - selezionare Dichiarazione delle variabili obbligatoria.

Questo fa sì che l'esecuzione del codice si interrompe con un errore di compilazione: Variabile non definita e ci indichi quale è la variabile non definita costringendoci di fatto a dichiararla.

Ci sarebbero da aggiungere altre note, ma per ora ci fermiamo qui e passiamo ad altre considerazioni.

 

Le matrici

Le matrici rappresentano un comodo modo per memorizzare un certo numero di dati correlati tra loro in un unico contenitore.

I nomi delle matrici seguono le stesse regole appena descritte per le variabili e sono soggette alle stesse regole della visibilità delle variabili.

In questa pagina tratteremo questi argomenti:

Dichiarazione

La loro dichiarazione nella sezione dichiarazione delle variabili, contrariamente alle normali variabili, non è facoltativa ma obbligatoria.

E' possibile dichiarare le matrici con la nota istruzione Dim posta nella sezione dichiarazione delle variabili, ma è possibile farlo anche con le parole chiave Public, Private, Static come per una qualsiasi altra variabile:

per una matrice statica:
Dim A (20), B(3 To 15, 8 To 21)

per una matrice dinamica:
Dim A (), B()

Matrici statiche e dinamiche

Le matrici statiche comprendono un determinato numero di elementi che deve essere conosciuto al momento della dichiarazione in modo che VBA possa riservare la quantità di memoria necessaria a contenerli tutti. Le matrici statiche una volta create non possono essere ridimensionate durante l'esecuzione.

La dichiarazione di una matrice statica, contrariamente a quella di una normale variabile è sempre obbligatoria e si effettua tramite la classica DIM indicando, tra parentesi, le dimensioni, che debbono essere tassativamente delle costanti numeriche (non si possono usare variabili per indicare le dimensioni).

 

Dim Matrice(1 to 20)
Dim Matrice(1 to 20, 1 to 5)

Dim Matrice(20)
Dim Matrice(20, 5)

Nelle prime due dichiarazioni abbiamo dichiarato sia il limite minimo che il limite massimo di ciascuna matrice, mentre nelle seconde due non viene espresso il limite inferiore per cui il numero di elementi è da intendersi aumentato di una unità (da 0 a 20 o da 0 a 5) salvo diversa direttiva impostata con Option base 1.

Ma se non conosciamo la dimensione da assegnare ad una matrice? Possiamo assegnarla in modo arbitraria rischiando di sottodimensionare o sovradimensionare la matrice. Ma perché correre questi rischi se VBA ci mette a disposizione un mezzo potente: le matrici dinamiche.

Le matrici dinamiche si comportano e vengono utilizzate come quelle statiche ma si rendono particolarmente utili se non si conosce a priori il numero di elementi da collezionare o se si prevede di doverne spesso cambiare le dimensioni durante l'esecuzione di una routine ed appunto per questo consentono di gestire la memoria in modo più efficiente evitando inutili sprechi di memoria o restrizioni nell'utilizzo.

La dichiarazione di una matrice dinamica si compie in due tempi:

sono due i metodi da usare per ridimensionare in modo dinamico una matrice:

Redim Matrice(20)
Redim Matrice(1 To 20)

oppure:

Redim Preserve Matrice(25)
Redim Preserve Matrice(1 To 25)

Usando la prima sintassi i dati eventualmente memorizzati nella matrice vendono persi.

Usando la seconda sintassi i dati eventualmente memorizzati nella matrice vengono conservati. Con questo metodo c'è tuttavia un limite. Se la matrice ha più di una dimensione è possibile modificare solo l'ultima dimensione.

Se si utilizza la parola chiave Preserve, è possibile modificare solo l'ultima dimensione della matrice e non il numero di dimensioni. Ad esempio:

  • se la matrice ha una sola dimensione, la modifica è possibile, in quando si tratta dell'unica e ultima dimensione
  • se la matrice ha due o più dimensioni, è possibile modificare solo l'ultima dimensione e mantenere il contenuto della matrice.

L'esempio che segue illustra come aumentare l'ultima dimensione di una matrice dinamica senza cancellare i dati nella matrice (dalla guida in linea)

ReDim X(10, 10, 10)
. . .
ReDim Preserve X(10, 10, 15)

A = 15
ReDim Preserve Matrice(1 To 10, 1 To A)

Questo implica una certa oculatezza nell'organizzare i dati in una matrice dinamica.

Normalmente siamo abituati a raccoglirere i dati usando le righe della matrice come record e le colonne come campi.

Volendo raccogliere i dati in una matrice in modo dinamico dobbiamo comportarci in modo da raccogliere i dati in modo che i record vadano lungo le colonne ed i campi lungo le righe.

mi spiego con un esempio.

Diciamo che questa è la rappresentazione grafica dei dati disposti normalmente, tra le righe i record e lungo le colonne i campi in una matrice dichiarata con uno dei seguenti modi:

Dim Matrice(1 To 5, 1 To 3)
ReDim Matrice(1 To 5, 1 To 3)

Pinco Pallino 55
Paperon De Paperoni 78
Paperino Paolino 31
Archimede Pitagorico 25
Caius De Cais 13

Con una matrice così disposta non è possibile aggiungere nuovi record, a meno di effettuare una semplice Redim con relativa perdita dei dati.

Se prevediamo di dover aggiungere nuovi record in una matrice in modo dinamico è necessario organizzare la matrice in un altro modo e precisamente come nello schema che segue:

Pinco Paperon Paperino Archimede Caius
Pallino De Paperoni Paolino Pitagorico De Cais
55 78 31 25 13

Con una matrice così disposta è possibile intervenire con ReDim Preserve per accogliere nuovi record.

In questa pagina esempiodi esempio vediamo un esempio di codice da usare con o senza Preserve.

 

Tipo di matrice

E' possibile e facoltativo dichiarare il tipo di matrice tramite la parola chiave As. Se non espressamente dichiarato si assume che la matrice sia di tipo Variant

Dimensione di una matrice

Una matrice può essere unidimensionale o multidimensionale (due, tre o più dimensioni fino al raggiungimento massimo di 60 indici).

Gli indici possono essere espressi con numeri o con variabili

A (20)
A (X)
A (10, Y, 10, ecc)

Molto raramente si avrà a che fare con matrici con più di due dimensioni (normalmente una per le righe o record, una per le colonne o campi).

Limite superiore ed inferiore

Normalmente nel dichiarare una matrice si dichiara il solo limite superiore (obbligatorio) lasciando sottinteso il limite inferiore (facoltativo) che per default è 0 (zero) o 1 (uno) se Option Base è impostato ad 1.

Dim A (20), B (10, 5, 10, ecc)

Contrariamente a quanto si è tentati di pensare, infatti, la numerazione di una matrice non comincia da 1 ma da 0 (zero) se non espressamente definito con Option Base. Infatti le matrici per default sono dotati di indici zero - based, ossia l'indice del primo elemento di una qualsiasi dimensione di una matrice è 0 (zero). Ma, se desideriamo partire da un limite inferiore che sia diverso da 0 (zero) si può agire in diversi modi:

E' possibile leggere le dimenzioni delle matrici usando la Funzione LBound per leggere la dimensione inferiore e la Funzione UBound per leggere la dimensione superiore.

Per leggere gli indici minimi e massimi di una matrice monodimensionale, Dim Matrice(1 To 10), è sufficiente:

A1 = LBound(Matrice)
A2 = UBound(Matrice)

Mentre per leggere gli indici minimi e massimi di matrici a più dimensioni, Dim Matrice(1 To 20, 1 To 10), è necessario indicare la dimensione di cui si vogliono leggere i valori:

A1 = LBound(Matrice, 1)
A2 = UBound(Matrice, 1)
A1 = LBound(Matrice, 2)
A2 = UBound(Matrice, 2)

 

Un consiglio:

Per evitare confusione è preferibile non usare la direttiva Option Explicit, quando possibile, e per definire il limite minimo diverso da 0 di una matrice è meglio usare il modo esplicito nella dichiarazione:

Dim Matrice(1 To 10, 1 To 10)

oppure

ReDim Nome(1 To R)

per evitarfe di preoccuparsi di trovare gli indici inferiori di una matrice.

Determinare il numero di elementi contenuti in una matrice

Per le matrici unidimensionali il numero di elementi in esse contenute è uguale alla differenza tra i valori minimo e massimo:

Dim A(20), B(1 To 20), C(4 To 20)
(20-0+1), (20-1+1),( 20-4+1)

mentre nelle matrici multidimensionali il numero di elementi in esse contenuti è dato dal prodotto del numero di elementi di ogni dimensione:

Dim C(2, 1 To 5, 4 To 25)
(2-0+1) * (5-1+1) * (25-4+1) =
3 * 5 * 22 = 330 elementi.

Creazione e lettura delle matrici

L'assegnazione e la lettura dei singoli dati nella matrice può essere fatta manualmente o tramite cicli.

Il numero che si riferisce al numero dell'elemento che si va a determinare o a leggere può essere rappresentato da un numero o da una variabile numerica.

Per l'assegnazione manuale:

NomeMese(1) = "Gennaio"
NomeMese(2) = "Febbraio"
NomeMese(3) = "Marzo"
NomeMese(4) = "Aprile"
' ecc.

oppure per l'assegnazione tramite cicli:

Dim Matrice(10, 10)
For A = 1 To 10
For B = 1 To 15
Matrice(A, B) = A * 10 + B
Next
Next

Per la lettura si può esprimere un valore contenuto in una matrice richiamandolo tramite il nome della matrice ed un numero significativo compreso nel suo indice:

MsgBox NomeMese(5)
MsgBox Matrice(7, 5)
Range("A1").Value = Matrice(Variabile)

oppure è possibile richiamare tutti i valori compresi nella matrice istruendo opportuni cicli. Il seguente esempio metto in una MessageBox il contenuto di una matrice:

Messaggio = "Questi sono i valori contenuti nella matrice C(1 To 10, 1 To 3)" & vbCr
For A = 1 To 10
For B = 1 To 3
Messaggio = Messaggio & C(A, B) & " "
Next
Messaggio = Messaggio & vbCr
Next
MsgBox Messaggio

Un altro modo di assegnazione e lettura di una matrice

E' possibile creare dinamicamente una matrice di elementi utilizzando la funzione Array

Il limite inferiore di una matrice creata utilizzando la funzione Array è comunque sempre zero - based se non diversamente specificato con l'istruzione Option Base. Se Array viene qualificato dal nome della libreria dei tipi (ad esempio VBA.Array), Option Base non influisce su Array.

Nota: In questo caso anche una variabile di tipo Variant non dichiarata (In ogni caso sconsigliato per i motivi esposti sopra) può comunque includere una matrice.

Assegnazione

Sub Prova()
Dim GiornoSettim, MioGiorno, T
GiornoSettim= Array("lun", "mar", "mer", "gio", "ven", "sab", "dom")

Lettura

Per la lettura usiamo anche in questo caso i soliti metodi di lettura manuale o tramite cicli anche se in questo caso è consigliabile l'uso delle funzioni LBound e UBound di cui parleremo nel prossimo paragrafo:

For T = LBound(GiornoSettim) To UBound(GiornoSettim)
MioGiorno = GiornoSettim(T)
Next
End Sub

Anche in questo caso, come specificato sopra, il limite inferiore della dimensione della matrice può essere 0 o 1

Conoscere le dimensioni di una matrice tramite le funzioni LBound e UBount

Alle volte può tornare utile conoscere le dimensioni di una matrice per evitare di elaborare meno elementi di quelli realmente contenuti nella matrice o di incorrere in errori di runtime per aver tentato di accedere agli elementi di una matrice indicando indici al di fuori delle reali dimensioni della matrice.

Per recuperare l'indice superiore ed inferiore di una matrice è possibile utilizzare le funzioni LBound e UBount.

La sintassi è: LBound(NomeMatrice). Ma se la matrice ha più di una dimensione occorre anche specificare la dimensione: UBound(NomeMatrice, 1) UBound(NomeMatrice, 2), ecc.

Ecco un esempio di utilizzo:

Sub ProvaMatrice()
' questa è una matrice a due dimensioni
Dim Indirizzi(1 To 100, 20)
' visualizza i valori minimo e massimo della prima dimensione: 1 e 100
MsgBox LBound(Indirizzi) & vbCr & UBound(Indirizzi)

' uguale a quella precedente
MsgBox LBound(Indirizzi, 1) & vbCr & UBound(Indirizzi, 1)

' visualizza i valori minimo e massimo della prima dimensione: 1 e 100
MsgBox LBound(Indirizzi, 2) & vbCr & UBound(Indirizzi, 2)

'visualizza il numero degli elementi della prima dimensione: 100
MsgBox "Numero elementi prima dimensione: " _
& (UBound(Indirizzi, 1) - LBound(Indirizzi, 1)) + 1

'visualizza il numero degli elementi della seconda dimensione: 21
MsgBox "Numero elementi seconda dimensione: " _
& (UBound(Indirizzi, 2) - LBound(Indirizzi, 2)) + 1

'visualizza il numero totale degli elementi memorizzati nella matrice: 100 * 21 = 2100
MsgBox "Totale elementi nella matrice: " _
& (UBound(Indirizzi, 1) - LBound(Indirizzi, 1)) + 1 & " * " _
& (UBound(Indirizzi, 2) - LBound(Indirizzi, 2)) + 1 & " = " _
& ((UBound(Indirizzi, 1) - LBound(Indirizzi, 1)) + 1) * _
((UBound(Indirizzi, 2) - LBound(Indirizzi, 2)) + 1)
End Sub

 

Trasferimento di una matrice ad un intervallo di celle

Ora vediamo come trasferire il contenuto di una matrice in un foglio di Excel.

Possiamo seguire due strade:

Per trasferire una matrice nel foglio senza usare un ciclo si può provare con questa routine. In questo caso è necessario che l'intervallo abbia le stesse dimensioni della matrice.

Se le dimensioni dell'intervallo che deve accogliere i dati è più piccolo rispetto alle dimensioni, i restanti dati della matrice verranno persi.

Se le dimensioni dell'intervallo è più grande rispetto alle dimensioni della matrice, nelle celle eccedenti verrà visualizzato il messaggio "#N/D".

Questa che segue è una possibile routine che, riempita preventivamente coi numeri di una tabellina, si può usare con questa metodica ed in questa pagina esempiopotete vedere i 3 possibili risultati usando intervalli che, rispetto alle dimensioni della matrice, possono essere più grandi o più piccoli.

Sub TrasferMatrInCelle()
Dim R, C
Dim Matrice(1 To 10, 1 To 10)
Dim Intervallo As Range
' viene creata una matrice che memorizza una tabella pitagorica
For R = 1 To 10
For C = 1 To 10
Matrice(R, C) = R * C
Next
Next
Riga = UBound(Matrice, 1)
Colonna = UBound(Matrice, 2)
Sheets("Foglio4").Select
' ora la matrice viene scaricata in un intervallo del foglio di calcolo
Range("B2").CurrentRegion.ClearContents
Set Intervallo = Range("B2").Resize(Riga, Colonna)
Intervallo.Value = Matrice
End Sub

 

Per trasferire una matrice nel foglio usando dei cicli Foe ... Next si potrebbe usare la routine che segue.

In questo caso sul foglio lo spazio occorrente viene preso in modo automatico. Unica cosa da conoscere è la prima cella che dovrà essere occupata dai dati della matrice.

Questo potrebbe essere una routine di esempio:

Sub MatriceInCelleConCicli()
Dim Matrice(1 To 10, 1 To 10)
Dim R, C, Riga, Colonna
Dim Intervallo As Range
' viene creata una matrice che memorizza una tabella pitagorica
For R = 1 To 10
For C = 1 To 10
Matrice(R, C) = R * C
Next
Next
Riga = UBound(Matrice, 1)
Colonna = UBound(Matrice, 2)
Sheets("Foglio4").Select
Range("B2").CurrentRegion.ClearContents
Set Intervallo = Range("B2")
' ora la matrice viene scaricata, a partire dalla cella B2, nel foglio di calcolo
For R = 1 To Riga
For C = 1 To Colonna
Intervallo(R, C) = Matrice(R, C)
Next
Next
End Sub

 

Matrici che contengono altre matrici

L'esempio di questo paragrafo si trova nel file allegato al primo foglio a cui è collegato il primo modulo, ma si trova anche al foglio 3 ed abbinato al modulo 5
Sub MatrInMatr()

Una particolarità non trascurabile è che le matrici possono contenere anche altre matrici. Infatti è possibile creare una matrice e riempirla con matrici, anziché con valori, a cui sono stati assegnati altri tipi di dati

For T = 1 To 10
A(T) = T
Next

For T = 1 To 10
B(T) = T + T * 10
Next

Altro(1) = A()
Altro(2) = B()

In questo caso abbiamo due matrici monodimensionali: A() - B()

Trasferiamo il contenuto delle due matrici in una terza matrice Altro() anch'essa monodimensionale (dichiarata cioè come Dim Altro(1 To 2) ) ma con il risultato da farla apparire bidimensionale.

La lettura di questo tipo di matrice viene effettuata come nell'esempio seguente:

MsgBox Altro(1)(6) & vbCr & Altro(2)(6)

Ecco di seguito Un esempio di utilizzazione di questa procedura.

Sub MatrInMatr()
Dim Posiz, Righe, Colonne, R, C
Dim Intervallo As Range
Dim Nome(), Indirizzo()
Dim Schedario(1 To 2)
Sheets("Foglio3").Select
'ricerca dell'intervallo dei dati
With ActiveSheet.UsedRange
Posiz = 1
While IsEmpty(.Cells(Posiz))
Posiz = Posiz + 1
Wend
'vengono determinate le righe e le colonne dell'intervallo trovato
Righe = .Cells(Posiz).CurrentRegion.Rows.Count
Colonne = .Cells(Posiz).CurrentRegion.Columns.Count
'all'intervallo trovato viene tolta la riga di intestazione
Set Intervallo = .Cells(Posiz).CurrentRegion.Offset(1, 0).Resize(Righe - 1, Colonne)
End With
'memorizzazione dei nomi e degli indirizzi in due distinte matrici
ReDim Nome(1 To Righe - 1)
ReDim Indirizzo(1 To Righe - 1)
Intervallo.Offset(0, 4).ClearContents
For R = 1 To Righe - 1
Nome(R) = Intervallo(R, 1).Value
Indirizzo(R) = Intervallo(R, 2).Value
Next
'unione delle due matrici in una terza matrice
Schedario(1) = Nome()
Schedario(2) = Indirizzo()

'questo per eseguire un test di lettura
For R = 1 To Righe - 1
Intervallo(R, 1).Offset(0, 4) = Schedario(1)(R)
Intervallo(R, 2).Offset(0, 4) = Schedario(2)(R)
Next
End Sub
Una nota personale:
Questo metodo non è di mio gradimento in quanto la gestione di una simile aggregazione di matrici si mostra più complessa della gestione di una normale matrice

 

Svuotare o eliminare una matrice per recuperare memoria

Le matrici occupano quantità di memoria abbastanza grandi. Basta dare un'occhiata al calcolo che ho fatto nel paragrafo Determinare il numero di elementi contenuti in una matrice:

Dim C(2, 1 To 5, 4 To 25)
(2-0+1) * (5-1+1) * (25-4+1) =
3 * 5 * 22 = 330 elementi.

Visto che con le matrici si fa presto ad occupare così tanta memoria alla fine dell'utilizzo delle matrici è meglio svuotarle o eliminarle per recuperare un po' di risorse.

Per fare questo VBA ci mette a disposizione una particolare istruzione: Erase.

Se viene applicata ad una matrice statica Erase si limita a cancellare solo gli elementi della matrice riportandola alle condizioni in cui era al momento della sua inizializzazione (Dim A (20,10)).

Se viene applicata ad una matrice dinamica la distrugge completamente.

Ho eseguito questo test per verificare quel che avviene realmente

Sub testPerErase()
Dim Matrice1(1 To 20)
Dim Matrice2()
Dim R, Max
'la creazione di una matrice statica
For R = 1 To 20
Matrice1(R) = R
Next
'creazione di una matrice dinamica
Max = 20
ReDim Matrice2(1 To Max)
For R = 1 To Max
Matrice2(R) = R
Next
'lettura dei limiti di entrambe le matrici
MsgBox LBound(Matrice1) & " - " & UBound(Matrice1)
MsgBox LBound(Matrice2) & " - " & UBound(Matrice2)
'distruzione delle matrici
Erase Matrice1
Erase Matrice2
'ulteriore lettura dei limiti delle matrici
'mentre la matrice statica, anche se svuotata dei valori mostra i suoi limiti

MsgBox LBound(Matrice1) & " - " & UBound(Matrice1)
'la matrice dinamica restituisce un errore, segno questo dell'avvenuta distruzione della stessa
MsgBox LBound(Matrice2) & " - " & UBound(Matrice2)
End Sub

 

Attenzione:

non tentate di leggere gli indirizzi delle matrici dopo Erase per non correre il rischio di ottenere degli errori come io volutamente ho fatto a solo scopo dimostrativo.

Da questo ne consegue che dopo il comando Erase, la matrice statica è stata semplicemente svuotata, ma conserva tutte le dimensioni e i relativi limiti, inferiori e superiori, mentre la matrice dinamica, invece è stata completamente eliminata.

Questo motivo mi fa prediligere il lavoro con le matrici dinamiche.