Ordinamento delle matrici in base a più campi

Ultima modifica: 30-05-2016

 

Sul problema delle matrici abbiamo già parlato abbondantemente, ma ci sono certi argomenti un po' difficili da essere compresi, per cui, anche se precedentemente ne abbiamo già parlato, ci torniamo su guardando il problema da un altro punto di vista.

Stiamo parlando dell'ordinamento di una matrice basato su un campo e dell'ordinamento di una matrice su più campi.

E' ovvio che la matrice di cui stiamo parlando è a 2 dimensioni: una per record (righe) ed una per campi (colonne).

Per Ordinamento di una matrice basato su un campo intendiamo dire che, data una matrice i cui campi sono: nome, cognome, CAP, città, prov, via, possiamo decidere di ordinare i dati contenuti nella matrice in base al Nome, o al Cognome, ecc.

Per Ordinamento di una matrice basato su più campi intendiamo dire che la stessa matrice possiamo ordinarla prima per un campo, poi per un secondo campo, quindi per un terzo campo e così via.

Nell'immagine che osserviamo qui sotto vediamo due tabelle: una a destra e l'altra a sinistra.

La tabella su cui lavoriamo sarà quella di destra. La tabella di sinistra ci servirà soltanto per controllare le modifiche avvenute nella matrice dopo i due ordinamenti effettuati ed esternate nella tabella di destra e servirà poi per essere trasferita così com'è nella zona di destra per un ulteriore test.

A B C D E F G H I J K L M N
1 nome cognome CAP città prov via nome cognome CAP città prov via
2 Luigi Boscati 14030 Milano TO VIA DEL TUSCOLANO 1 Luigi Boscati 14030 Milano TO VIA DEL TUSCOLANO 1
3 Carlo Rizzi 86086 Verezzo PG VIA DEL TUSCOLANO 1 Andrea Boscati 36010 Verezzo PG VIA MARCHESANE, 232
4 Andrea Boscati 36010 Verezzo PG VIA MARCHESANE, 232 Carlo Boscati 14030 Milano PC VIA SABIN 34/2
5 Carlo Boscati 14030 Milano PC VIA SABIN 34/2 Luigi Boscati 54018 Milano PC VIA DEL TUSCOLANO 1
6 Andrea Rizzi 09010 Milano TO VIA DEL TUSCOLANO 1 Carlo Boscati 30010 Verezzo TO VIA SABIN 34/2
7 Andrea Maggi 43050 Verezzo PG VIA GRAMSCI 14 A Andrea Boscati 85054 Milano TO VIA GRAMSCI 14 A
8 Andrea Zazzera 14030 Napoli PG VIA TRAVERSETOLO 2/A Carlo Boscati 37020 Verezzo PG VIA CAVOUR
9 Luigi Boscati 54018 Milano PC VIA DEL TUSCOLANO 1 Luigi Boscati 08038 Milano PG VIA DEL TUSCOLANO 1
10 Carlo Boscati 30010 Verezzo TO VIA SABIN 34/2 Andrea Boscati 12080 Napoli TO VIA BREDINA 6
11 Carlo Rizzi 14030 Verezzo PG VIA DEI GERANI Andrea Boscati 14030 Verezzo PG VIA PO, 31
12 Andrea Boscati 85054 Milano TO VIA GRAMSCI 14 A Carlo Boscati 16027 Milano LU VIA BRIGONI, 6
13 Carlo Boscati 37020 Verezzo PG VIA CAVOUR Andrea Boscati 14030 Napoli TO VIA DEI GERANI
14 Carlo Zazzera 14030 Napoli TO VIA SABIN 34/2 Carlo Boscati 14030 Napoli TO VIA DEL TUSCOLANO 1
15 Luigi Boscati 08038 Milano PG VIA DEL TUSCOLANO 1 Luigi Boscati 65016 Milano TO VIA DELLA CHIMICA N.9
16 Andrea Boscati 12080 Napoli TO VIA BREDINA 6 Enrico Boscati 14030 Verezzo TO VIA CAVOUR
17 Luigi Rizzi 14030 Napoli TO VIA GIUSEPPE DI VITTORIO, 41 Carlo Boscati 06033 Verezzo LU VIA DEL TUSCOLANO 1
18 Andrea Boscati 14030 Verezzo PG VIA PO, 31 Enrico Boscati 14030 Napoli LU STRADA MARCHESANE 434
19 Carlo Boscati 16027 Milano LU VIA BRIGONI, 6 Luigi Maggi 01039 Napoli TO VIA SABIN, 34/2
20 Andrea Boscati 14030 Napoli TO VIA DEI GERANI Andrea Maggi 43050 Verezzo PG VIA GRAMSCI 14 A
21 Carlo Boscati 14030 Napoli TO VIA DEL TUSCOLANO 1 Enrico Maggi 14030 Verezzo LU VIA SABIN 34/2
22 Luigi Boscati 65016 Milano TO VIA DELLA CHIMICA N.9 Carlo Maggi 01030 Napoli LU VIA SABIN 34/2
23 Enrico Boscati 14030 Verezzo TO VIA CAVOUR Andrea Rizzi 09010 Milano TO VIA DEL TUSCOLANO 1
24 Carlo Rizzi 87038 Napoli PG VIA MATTEO RICCI 12 Luigi Rizzi 14030 Napoli TO VIA GIUSEPPE DI VITTORIO, 41
25 Enrico Maggi 14030 Verezzo LU VIA SABIN 34/2 Carlo Rizzi 86086 Verezzo PG VIA DEL TUSCOLANO 1
26 Luigi Maggi 01039 Napoli TO VIA SABIN, 34/2 Carlo Rizzi 14030 Verezzo PG VIA DEI GERANI
27 Carlo Boscati 06033 Verezzo LU VIA DEL TUSCOLANO 1 Carlo Rizzi 87038 Napoli PG VIA MATTEO RICCI 12
28 Enrico Boscati 14030 Napoli LU STRADA MARCHESANE 434 Carlo Zazzera 14030 Napoli TO VIA SABIN 34/2
29 Carlo Maggi 01030 Napoli LU VIA SABIN 34/2 Andrea Zazzera 14030 Napoli PG VIA TRAVERSETOLO 2/A

 

Sul foglio poniamo 3 pulsanti che attivano altrettante routines create nei modulo del VBA.

ordinamento sort array

Trasferisci a lato >>>

Siamo in un foglio di studio, quindi questo pulsante servirà a spostare nella tabella di destra i dati della tabella di sinistra che rimarranno sempre invariati (i lavori dimostrativi verranno eseguiti silla tabella di destra)

il pulsante attiva la routine Sub Trasferisci()

Ordina Matrice in base a più campi (tipo query)

Questo pulsante attiva la routine Sub OrdinaMatrice2()

Ordina Matrice in base ad un campo (classica)

Quest'ultimo pulsante attiva la routine Sub OrdinaMatrice()

 

Sub Trasferisci()

Per rinfrescare la tabella di destra dopo ogni prova eseguita usiamo questa routine situata nel Modulo1.

Su questa non c'è molto da dire:

una volta definiti i due intervalli (origine e destinazione) si sposta il contenuto della tabella di sinistra nell'intervallo di destra con Tabella.Copy Destination:=Dest

Sub Trasferisci()
Dim URiga, NRec, NCamp
Dim R, R1, R2, C
Dim Matr, Campo, Msg, VarTemp
Dim Tabella As Range, Dest As Range
Sheets("matr2dimens").Activate
URiga = Range("A1").End(xlDown).Row
NRec = URiga - 1
NCamp = Range("A1").End(xlToRight).Column
Set Tabella = Range("A1").CurrentRegion.Offset(1, 0).Resize(NRec, NCamp)
Set Dest = Tabella.Offset(0, 8).Resize(1, 1)
Tabella.Copy Destination:=Dest
End Sub

Sub OrdinaMatrice2()

Cominciamo da questa routine, anch'essa scritta nel Modulo1.

I commenti per le fasi più salienti il codice è stato abbondantemente commentato

Sub OrdinaMatrice2()
Dim URiga, NRec, NCamp
Dim R, C, R1
Dim CampiRec, CRec
Dim Tabella As Range
Dim Nome1, Nome2, VarTemp
Sheets("matr2dimens").Activate
URiga = Range("A1").End(xlDown).Row
NRec = URiga - 1
NCamp = Range("A1").End(xlToRight).Column
' viene definito l'intervallo da cui verranno prelevati i dati
Set Tabella = Range("A1").CurrentRegion.Offset(1, 0).Resize(NRec, NCamp)
' vengono memorizzati nella matrice i dati che vengono prelevati dalla tabella di sinistra
ReDim Matr(1 To NRec, 1 To NCamp)
Matr = Tabella
' viene aperta la userform per la scelta dei campi da ordinare
UserForm1.Show
' fatte le scelte dalla userform si torna a questo punto
On Error Resume Next
CampiRec = UBound(ICampi)
' in caso di errori si esce dalla routine
If Err <> 0 Then
Err = 0
MsgBox "Operazione annullata", vbExclamation, "Avviso"
Exit Sub
End If
' si procede all'ordinamento col metodo Bubble Sort
' il confronto viene eseguito comparando le stringhe opportunamente create
' *********************************************************************************
For R = 1 To NRec - 1
For R1 = R + 1 To NRec
Nome1 = "": Nome2 = ""
For CRec = 1 To CampiRec ' sono i campi scelti per l'ordinamento
' Per semplificare la procedura di ordinamento vengono concatenati i campi scelti nella userform creando una unica stringa su cui lavorare
Nome1 = Nome1 & Matr(R, ICampi(CRec))
Nome2 = Nome2 & Matr(R1, ICampi(CRec))
Next
If Nome1 > Nome2 Then
For C = 1 To NCamp
VarTemp = Matr(R, C)
Matr(R, C) = Matr(R1, C)
Matr(R1, C) = VarTemp
Next
End If
Next
Next
' *********************************************************************************
' come sono stati prelevati i dati dal foglio alla matrice, così vengono restituiti al foglio, nella tabella di destra, ma ordinati
Tabella.Offset(0, 8) = Matr
End Sub

Questa è la userform usata per effettuare le scelte

ordinamento su piu campi

In questa userform

La gestione di questa userform è molto semplice e richiede poco codice.

Vengono gestiti solo questi 3 eventi:

Vediamole brevemente

Attivazione della userform

Quando viene chiamata la userform, nell'evento Activate, le combobox vengono popolate con le intestazioni di colonna per permetterne l'immediato utilizzo.

Private Sub UserForm_Activate()
Dim UCol, C
ComboBox1.Clear
ComboBox2.Clear
ComboBox3.Clear
ComboBox4.Clear
ComboBox5.Clear
With Sheets("matr2dimens")
UCol = Range("A1").End(xlToRight).Column
For C = 1 To UCol
ComboBox1.AddItem .Cells(1, C)
ComboBox2.AddItem .Cells(1, C)
ComboBox3.AddItem .Cells(1, C)
ComboBox4.AddItem .Cells(1, C)
ComboBox5.AddItem .Cells(1, C)
Next
End With
End Sub

Dopo aver effettuato le scelte

Nella userform ci sono cinque ComboBox, una per ogni intestazione di colonna e due pulsanti: si può scegliere il pulsante OK o Annulla.

Questa è la routine per OK

Private Sub CommandButton1_Click()
Dim C1, R, R1
R = 0
For C1 = 1 To 5
If Controls("ComboBox" & C1) <> "" Then
R = R + 1
ReDim Preserve ICampi(1 To R)
ICampi(R) = Controls("ComboBox" & C1).ListIndex + 1
End If
Next
If IsEmpty(ICampi) Then
Unload UserForm1
Set UserForm1 = Nothing
Exit Sub
End If
If UBound(ICampi) > 1 Then
For R = 1 To UBound(ICampi) - 1
For R1 = R + 1 To UBound(ICampi)
If ICampi(R) = ICampi(R1) Then
MsgBox "Effettuate scelte errate" & vbCr & "Ripetere le scelte", vbCritical, "Attenzione"
Exit Sub
End If
Next
Next
End If
Unload UserForm1
Set UserForm1 = Nothing
End Sub

Per annullare l'operazione

Se si sceglie Annulla la userform viene chiusa

Private Sub CommandButton2_Click()
Erase ICampi
Unload UserForm1
Set UserForm1 = Nothing
End Sub

 

Ordinamento di una matrice in base ad un unico campo

Questa è molto più semplice della precedente. Non si fa uso di una userform ma di una semplice InputBox che chiede di scegliere uno tra i numeri associati a ciascuna delle intestazioni di colonna.

La prima parte è molto simile alla prima parte della routine precedente

Sub OrdinaMatrice()
Dim URiga, NRec, NCamp
Dim R, R1, R2, C, CA
Dim Matr, INomi, ICampi
Dim Campo, Msg, VarTemp
Dim Tabella As Range, Dest As Range
Sheets("matr2dimens").Activate
URiga = Range("A1").End(xlDown).Row
NRec = URiga - 1
NCamp = Range("A1").End(xlToRight).Column
Set Tabella = Range("A1").CurrentRegion.Offset(1, 0).Resize(NRec, NCamp)
ReDim Matr(1 To NRec, 1 To NCamp)
' vengono memorizzati nella matrice i dati che vengono letti dalla seconda tabella
Matr = Tabella
' viene preparato il messaggio da visualizzare nella InputBox .... il messaggio consiste nell'elenco delle intestazioni di colonna affiancati da numeri
Msg = "Scelta del campo (scegliere un numero!)" & vbCr
For C = 1 To NCamp
Msg = Msg & C & " - " & Cells(1, C) & vbCr
Next
' si chiede quale campo si intende ordinare
Campo = InputBox(Msg, "Attenzione")
If Val(Campo) < 1 Or Val(Campo) > NCamp Then
MsgBox "Operazione annullata", vbExclamation, "Avviso"
Exit Sub
End If
' si procede all'ordinamento col metodo Bubble Sort
' il confronto viene eseguito comparando i dati che si trovano nella colonna Campo
' *********************************************************************************
For R = 1 To NRec - 1
For R1 = R + 1 To NRec
If Matr(R, Campo) > Matr(R1, Campo) Then
' si effettua lo scambio di tutti i campi dei due record coinvolti
For C = 1 To NCamp
VarTemp = Matr(R, C)
Matr(R, C) = Matr(R1, C)
Matr(R1, C) = VarTemp
Next
End If
Next
Next
' *********************************************************************************
' si visualizza il contenuto ordinato della matrice
Tabella.Offset(0, 8) = Matr
End Sub

Questa sarà la InputBox dalla quale verrà effettuata la scelta del campo da ordinare:

ordinamento una dimensione

 

Alcune note esplicative sul codice relativo all'ordinamento

Prima di concludere vorrei ora mettere a confronto le istruzioni principali usate per i due tipi di ordinamento.

Ordinamento eseguito usando il metodo del BubbleSort
Per il semplice ordinamento basato su un solo campo Per l'ordinamento basato su più campi a scelta dell'utente
For R = 1 To NRec - 1
For R1 = R + 1 To NRec
If Matr(R, Campo) > Matr(R1, Campo) Then
For C = 1 To NCamp
VarTemp = Matr(R, C)
Matr(R, C) = Matr(R1, C)
Matr(R1, C) = VarTemp
Next
End If
Next
Next
For R = 1 To NRec - 1
For R1 = R + 1 To NRec
Nome1 = "": Nome2 = ""
For CRec = 1 To CampiRec
' ora i valori dei campi sono concatenati nell'ordine scelto precedentemente nella UserForm
Nome1 = Nome1 & Matr(R, ICampi(CRec))
Nome2 = Nome2 & Matr(R1, ICampi(CRec))
Next
If Nome1 > Nome2 Then
For C = 1 To NCamp
VarTemp = Matr(R, C)
Matr(R, C) = Matr(R1, C)
Matr(R1, C) = VarTemp
Next
End If
Next
Next

 

Prima osservazione: nello schema di sinistra, ordinamento in base ad un campo, vengono confrontati i valori contenuti nella matrice, mentre nello schema di destra, ordinamento basato su più campi, prima di eseguire il confronto viene eseguito un concatenamento di tutti i valori contenuti nei campi precedentemente indicati e selezionati nella UserForm

Seconda osservazione: in entrambi i casi, lo scambio che si andrà ad effettuare avverrà con queste modalità:

 

modalità ordinamento

si pone in una variabile temporanea il contenuto della locazione puntata da R e C

si copia nella locazione puntata da R(iga) e C(olonna) il contenuto della locazione puntata da R1 e C

si copia nella locazione puntata da R1 e C il contenuto precedentemente salvato nella variabile temporanea

For C = 1 To NCamp
VarTemp = Matr(R, C)
Matr(R, C) = Matr(R1, C)
Matr(R1, C) = VarTemp
Next

 

E' ovvio che nel modulo che contiene le istruzioni appena esaminate, circondate ed evidenziate da una serie di asterischi, è stato necessario aggiungere altro codice per memorizzare i dati nella matrice e per la scelta del campo o dei campi da ordinare. Per il commento a queste istruzioni aggiuntive si rimanda agli articoli già apparsi nel sito o, se proprio necessario, sarà fatto in un altro momento.

Allego il file che ho usato per testare ed eseguire gli ordinamenti nella matrice. Il file liberamente scaricabile e consultabile è ordinamento_in_base_piu_campi.zip (22KB).