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 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:
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()
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:
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 di esempio vediamo un esempio di codice da usare con o senza Preserve.
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
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).
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.
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.
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
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
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:
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 potete vedere i 3 possibili risultati usando intervalli che, rispetto alle dimensioni della matrice, possono essere più grandi o più piccoli.
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:
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.
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
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.