Ordinare i dati ...

Chi ha a che fare con elenchi di qualsiasi genere prima o poi s'imbatte nella necessità di ordinare alfabeticamente o numericamente i propri dati.

 

 

La funzione di Excel

Spesso è sufficiente la funzione di Excel:

Range(Cells(1, 2), Cells(NumMax, 2)).Sort Key1:=Range("B1"), _
order1:=xlAscending, _
Header:=xlGuess, _
OrderCustom:=1, _
MatchCase:=False, _
Orientation:=xlTopToBottom, _
DataOption1:=xlSortNormal

se si desidera un ordinamento discendente, anziché ascendente si può sostituire order1:=xlAscending con order1:=xlDescending che tra l'altro è abbastanza veloce, ma non abbastanza, secondo me.

 

 

Tecniche alternative

A volte per motivi che non sto ad elencare perché sono innumerevoli, possiamo desiderare di costruirci o usare tecniche personalizzate per eseguire certi tipi di ordinamento

ne cito solo alcuni:

  1. in un elenco che mostra delle date possiamo volere un ordinamento per mese o per giorno anziché semplicemente per data
  2. in un elenco che mostra nome e cognome messi in una unica cella possiamo volere un ordinamento per cognome senza dover scomporre il nome ed il cognome su due celle distinte
  3. in un elenco di numeri possiamo volere un ordinamento per decine, centinaia, o altro anziché un normale ordinamento numerico
  4. ecc.

E' qui che cominciano i guai: infatti spesso ci accorgiamo di dover lottare con un fattore molto fastidioso: il tempo.

 

Nel mio posto di lavoro, essendo, tra l'altro, responsabile dell'archivio, spesso ho a che fare con un numero elevato di cartelle da ordinare numericamente e deporre negli scaffali.

Il lavoro è lungo e noioso per cui ho il tempo di pensare. Il metodo che uso (manualmente, naturalmente, perché diversamente non si può fare) non è simile a quello famosissimo del Bubble-sort, che per molto tempo ho usato nelle mie applicazioni già dai tempi del Commodore, ma un altro non documentato nelle varie guide.

Le tecniche di ordinamento sono tante ma quella presentata più frequentemente in qualche manuale è la tecnica del Bubble-sort che di solito è la più facile da gestire e da capire. Il nome Bubble-sort è dovuto al fatto che gli elementi da ordinare si muovono lentamente verso l'alto (come bolle) fino a raggiungere la loro posizione definitiva nell'elenco ordinato.

Allora mi sono messo ad analizzare le azioni che compivo e, notando che sono decisamente migliori del Bubble-sort, mi è nata l'idea di creare una sorta di metodo personale da applicare ai dati da ordinare.

Mi sono messo all'opera, ho confrontato i due metodi e, visto che quello mio, specialmente con un numero elevato di elementi da ordinare, è notevolmente migliore di quello classico, è nata quella che sto per presentare.

 

 

La tecnica di Bubble-sort

Questo è il famoso Bubble-sort eseguito sui valori presenti nelle celle del foglio:

For I = 1 To NumMax - 1
For A = I + 1 To NumMax
If Cells(A, 1) > Cells(I, 1) Then
Numero = Cells(I, 1)
Cells(I, 1) = Cells(A, 1)
Cells(A, 1) = Numero
End If
Next
Next

Con questo metodo (il Bubble-sort):

  1. si imposta un primo ciclo che va dal primo elemento fino al NumMax - 1 (il penultimo elemento)
  2. For I = 1 To NumMax - 1
  3. si imposta immediatamente dopo un secondo ciclo nidificato nel primo che va dalla posizione immediatamente successiva a quella indicata dal primo ciclo fino all'ultimo elemento
    quando la I del primo ciclo ha valore = 1 la A del ciclo interno inizia con valore = 2
    quando la I del primo ciclo ha valore = 2 la A del ciclo interno inizia con valore = 3
    ecc.
    For A = I + 1 To NumMax
    i due cicli così impostati fanno in modo che ogni elemento puntato dal ciclo esterno I possa essere confrontato con ogni elemento puntato dal ciclo interno A
  4. si esegue il confronto tra la cella puntata dal ciclo esterno e quella puntata dal ciclo interno
    If Cells(A, 1) > Cells(I, 1) Then
  5. se il confronto supera il test, cioè se l'elemento contenuto nella cella puntata dal ciclo esterno è maggiore, in questo caso, di quello puntato dal ciclo interno avviene lo scambio tra i due elementi
    1. si trasferisce in una variabile temporanea (Numero) il contenuto della cella puntata dal ciclo esterno
    2. si scrive nella cella puntata dal ciclo esterno il contenuto della cella puntata dal ciclo interno
    3. si scrive nella cella puntata dal ciclo interno il contenuto della variabile temporanea
      Numero = Cells(I, 1)
      Cells(I, 1) = Cells(A, 1)
      Cells(A, 1) = Numero

      (per quanto mi sia sforzato di documentarmi in materia sarebbe sufficiente una istruzione del tipo:
      swap(x,y) presente nel linguaggio C++ ma assente nel nostro VB o VBA)
  6. compiuta questa operazione il ciclo interno continua il suo giro e ad ogni passo viene eseguito il confronto descritto nei punti 3, 4 e 5
  7. tutto questo avviene fino alla fine del ciclo interno
  8. finito il ciclo interno viene incrementato il ciclo esterno e si prosegue dal punto 3

 

 

La mia nuova tecnica

Questo è il metodo da me elaborato in seguito alla mia esperienza lavorativa:

For I = 2 To NumMax
       If Cells(I, 1) > Cells(I - 1, 1) Then
              A = 1
             Do Until Cells(I, 1) > Cells(A, 1)
                   A = A + 1
             Loop
            Fine = A
            Numero = Cells(I, 1)
            For A = I To Fine + 1 Step -1
                  Cells(A, 1) = Cells(A - 1, 1)
           Next
           Cells(Fine, 1) = Numero
      End If
Next

  1. Si inizia impostando un unico ciclo che va dal secondo elemento fino all'ultimo elemento (NumMax)
    For I = 2 To NumMax
  2. si effettua il confronto tra la cella puntata dal ciclo e quella immediatamente prima
    If Cells(I, 1) > Cells(I - 1, 1) Then
  3. se il confronto ha esito positivo si va a confrontare l'elemento della cella puntata dal ciclo con tutti gli altri a partire dal primo fintanto che non si trova un elemento che abbia un valore inferiore a quello in esame (se non esiste alcun elemento che soddisfa questa condizione c'è sempre l'elemento superiore a quello in esame che soddisfa tale condizione e che fa cessare i confronti come nel seguente esempio)
    A = 1
    Do Until Cells(I, 1) > Cells(A, 1)
    A = A + 1
    Loop
    Fine = A
  4. si mette in una variabile temporanea il contenuto dell'elemento puntato dal ciclo esterno
    Numero = Cells(I, 1)
  5. a questo punto si imposta un altro ciclo che va dalla cella puntata dal primo ciclo fino a quella immediatamente sotto (Fine +1) a quella appena trovata (Fine) con step (passo) -1 (il valore del primo ciclo è superiore al valore che ha la variabile Fine appena trovata)
    For A = I To Fine + 1 Step -1
  6. si fanno scivolare in basso tutti gli elementi trovati durante quest'ultimo ciclo, compreso l'elemento puntato dalla variabile temporanea Fine (Cells(A - 1, 1))
    Cells(A, 1) = Cells(A - 1, 1)
    Next

    nell'esempio sopra illustrato questo passaggio si nota meglio nelle colonne I - M
  7. nella posizione puntata dalla variabile Fine si copia il valore precedentemente puntato dal primo ciclo e temporaneamente memorizzato in una variabile temporanea
    Cells(Fine, 1) = Numero
  8. finita l'operazione il primo ciclo incrementa e si ricomincia dal punto n° 2

La differenza tra le due routines sta in questo:

  1. nella prima i due cicli vengono comunque eseguiti fino alla fine, anche se la condizione posta non si verifica
  2. nella seconda si esegue un unico ciclo e, solo se si verifica la condizione, avviene la ricerca di un altra verifica, lo slittamento del blocco di elementi tra i due in esame e lo scambio degli elementi in esame

 

Ordinamento Crescente & Decrescente

Entrambi i metodi sopra esposti operano l'ordinamento in senso decrescente (dal più grande al più piccolo). Se volessimo un tipo di ordinamento inverso (dal più piccolo al più grande) basta semplicemente invertire l'operatore di confronto sopra usato da > (maggiore) a < (minore).

Esempio: dove leggiamo:

If Numero > Cells(I - 1, 1) Then

basta scrivere:

If Numero < Cells(I - 1, 1) Then

 

 

Il fattore Tempo: Celle o variabili?

Fin qui le considerazioni che abbiamo fatto sui due metodi di ordinamento.

Siccome parliamo di economia di tempo è d'obbligo a questo punto un'altra considerazione:

è più conveniente lavorare direttamente sulle celle o su variabili?

Se i dati sono pochi (alcune decine, forse anche alcune centinaia, di elementi da ordinare) è ovvio che qualsiasi metodo va bene.

Ho svolto delle indagini e confortato dall'esito delle mie prove e da ciò che si legge in alcuni manuali, sono giunto a questa conclusione:

ove possibile è preferibile lavorare con le variabili. Ma se i dati da elaborare sono molti, dove troviamo tutte le variabili necessarie e quanti nomi di variabili dobbiamo tenere a mente?

In questo caso la prima cosa utile che ci viene in mente sono le matrici.

Siccome in questa sede, per non annoiare ulteriormente chi mi legge, non parlerò delle tecniche dell'uso delle matrici, se di queste tecniche qualcuno dovesse aver bisogno, vi rimando agli articoli specifici.

L'uso di questo mezzo che il linguaggio di programmazione ci offre ci permetterà di raggiungere il nostro scopo.

 

Innanzitutto per usare una matrice dobbiamo inizializzarla e, a seconda i casi possiamo fare uso di una matrice statica o una matrice dinamica.

Se facciamo uso di una matrice statica è sufficiente un banale Dim Matrice(1 To 100) o quello che vogliamo.

Se facciamo uso di una matrice dinamica iniziamo con Dim Matrice() (o qualsiasi nome vogliamo)

quindi contiamo il numero di elementi che vogliamo mettere nella matrice con uno dei metodi più appropriati come per esempio questo:

NumMax= Range(Range("B1"), Range("B1").End(xlDown)).Count

una volta ottenuto il numero di elementi da mettere nella matrice diamo alla nostra matrice la giusta dimensione con:

ReDim Matrice(1 To NumMax)

a questo punto siamo pronti a riempire la nostra matrice.

Ecco quindi l'inizio della routine per copiare il contenuto delle celle nella matrice:

Dim Matrice()
' seguono altre eventuali dichiarazioni di variabili
NumMax= Range(Range("B1"), Range("B1").End(xlDown)).Count
ReDim Matrice(1 To NumMax)
For I = 1 To NumMax
Matrice(I) = Cells(I, 2)
Next

A questo punto siamo pronti per lavorare con la nostra matrice.

Vediamo perciò di adattare una delle due routines appena esposte alle nostre necessità. Prendiamo come esempio la seconda delle due esposte (che poi è, in ogni caso, quella che a questo punto propongo) e, tanto per testare i tempi di esecuzione, la applichiamo prima all'elenco residente sul foglio, poi alla matrice.

il metodo applicato direttamente sul foglio il metodo applicato ad una matrice
  raccolta dei dati nella matrice
 
For I = 1 To NumMax
Matrice(I) = Cells(I, 2)
Next
For I = 2 To NumMax
If Cells(I, 1) > Cells(I - 1, 1) Then
A = 1
Do Until Cells(I, 1) > Cells(A, 1)
A = A + 1
Loop
Fine = A
Numero = Cells(I, 1)
For A = I To Fine + 1 Step -1
Cells(A, 1) = Cells(A - 1, 1)
Next
Cells(Fine, 1) = Numero
End If
Next
For I = 2 To NumMax
If Matrice(I) > Matrice(I - 1) Then
A = 1
Do Until Matrice(I) > Matrice(A)
A = A + 1
Loop
Fine = A
Numero = Matrice(I)
For A = I To Fine + 1 Step -1
Matrice(A) = Matrice(A - 1)
Next
Matrice(Fine) = Numero
End If
Next
  trascrizione dei dati ordinati sul foglio
 
For I = 1 To NumMax
Cells(I, 6) = Matrice(I)
Next

Come spesso dico in queste circostanze, se i dati sono pochi, la scelta di uno o dell'altro metodo è ininfluente, quindi ognuno potrà lavorare come più gli aggrada. Ma se il numero dei dati da elaborare sono nell'ordine di alcune migliaia io insisto sempre nell'affermare che lavorare con le matrici fa risparmiare molto tempo prezioso.

 

 

Ordinare più colonne

Se quel che è stato detto fin qui è chiaro si può continuare con la lettura del seguente paragrafo, altrimenti consiglio di rileggere ancora quanto fin qui è stato scritto perché le cose si potrebbero complicare alquanto.

Punto primo

Non è più sufficiente creare una matrice ad una sola dimensione, ma ne dobbiamo creare una Matrice a più dimensioni e ci comportiamo così:

Dim Matrice()
' seguono altre eventuali dichiarazioni di variabili
NumMax= Range(Range("B1"), Range("B1").End(xlDown)).Count
ReDim Matrice(1 To NumMax, 1 To 4)
For I = 1 To NumMax
Matrice(I,1) = Cells(I + 1, 1)
Matrice(I,2) = Cells(I + 1, 2)
Matrice(I,3) = Cells(I + 1, 3)
Matrice(I,4) = Cells(I + 1, 4)
Next

Perché quel Cells(I + 1, 1)? Ma semplice. Perché se abbiamo un elenco a più colonne, senz'altro avremmo anche delle intestazioni di colonna e non vogliamo inserire nella matrice anche le intestazioni di colonna.

 

Punto secondo

alcune modifiche le dobbiamo apportare anche nella routine di ordinamento:

For I = 2 To NumMax
If Matrice(I,1) > Matrice(I - 1,1) Then ' se non è l'elemento che sta nella prima colonna
' a dover essere ordinato invece di 1 indicheremo il numero della colonna interessata
' all'ordinamento

A = 1
Do Until Matrice(I,1) > Matrice(A,1) ' stessa nota anche in questa istruzione
A = A + 1
Loop
Fine = A
A1 = Matrice(I, 1) ' poniamo in variabili temporali tutto il contenuto della riga
A2 = Matrice(I, 2) ' puntata dal contatore del ciclo I
A3 = Matrice(I, 3)
A4 = Matrice(I, 4)
For A = I To Fine + 1 Step -1
Matrice(A, 1) = Matrice(A - 1, 1) ' spostiamo in basso tutto il contenuto
Matrice(A, 2) = Matrice(A - 1, 2) ' delle righe puntate dal ciclo A
Matrice(A, 3) = Matrice(A - 1, 3)
Matrice(A, 4) = Matrice(A - 1, 4)
Next
Matrice(Fine, 1) = A1 ' mettiamo nella riga puntata dalla variabile Fine il contenuto
Matrice(Fine, 2) = A2 ' delle variabili temporali
Matrice(Fine, 3) = A3
Matrice(Fine, 4) = A4
End If
Next
'Modifichiamo anche le istruzioni che vanno a scrivere sul foglio:
For I = 1 To NumMax
Cells(I + 1, 6) = Matrice(I, 1) ' ci ricordiamo del motivo di quel I + 1?
Cells(I + 1, 7) = Matrice(I, 2)
Cells(I + 1, 8) = Matrice(I, 3)
Cells(I + 1, 9) = Matrice(I, 4)
Next

 

 

Conclusioni

Come conclusione ho da fare una semplice annotazione: se l'ordinamento deve essere eseguito su un numero esiguo di elementi un metodo vale l'altro e potremo scegliere quello che più ci aggrada. Ma se dobbiamo agire su un numero consistente di elementi è meglio ponderare bene la cosa e scegliere il metodo più idoneo.

 

Il file di esempio file da scaricare (ordinamento.zip)

Dopo questa chiacchierata il mio file di Esempio è quasi d'obbligo, ma con esso anche alcune note su come leggerlo; per l'uso penso non ci siano problemi: è sufficiente premere i pulsanti che ho sistemato nei vari fogli.

  1. In ogni foglio eseguo una dimostrazione di uno o due metodi di ordinamento, annoto il numero di elementi che ho deciso di ordinare, ne cronometro il tempo impiegato per l'ordinamento.
  2. Per tutti i fogli, eccetto quello denominato "ascendente", ho scelto di eseguire l'ordinamento decrescente degli elementi (dall'elemento più grande al più piccolo).
  3. In tutti i fogli pongo nella prima colonna, o nel primo gruppo di colonne, (quella più a sinistra) gli elementi in maniera casuale. Questi elementi non verranno mai ordinati per lasciare la possibilità di confrontarli con quelli ordinati posti nelle restanti colonne o gruppi di colonne
  4. Ne consegue che gli elementi ordinati saranno quelli posti nelle altre colonne o gruppi di colonne.

Spero che queste semplici spiegazioni siano sufficienti a spiegare lo spirito del file che offro alla vostra sperimentazione.

 

Il file da scaricare: file da scaricare (ordinamento.zip) (30KB)