Ottenere valori casuali univoci (non ripetuti) tra due valori minimo e massimo

Alcuni utenti mi hanno chiesto se esiste un modo per ottenere con Excel una sequenza di valori casuali non ripetuti. Infatti la funzione =casuale() restituisce si dei valori casuali ma essi si ripetono.

Ovviamente è possibile farlo e ci possono essere diverse soluzioni. La soluzione che propongo è una funzione personalizzata che ho chiamato =estrai_numero(minimomassimoche inserita in una cella genera un numero casuale intero tra un valore minimo e massimo specificati senza ripeterlo finchè non ha generato tutti quelli possibili tra il minimo ed il massimo, dopodiché ricomincia con un’altra sequenza casuale diversa da quella precedente.

Ad esempio, se inserendo nelle seguenti celle:

  • A1 =estrai_numero(1;4) otterrai un numero intero tra 1 e 4, ad esempio 3
  • A2 = estrai_numero(1;4) otterrai uno di questi numeri 1,2 e 4 ma non 3 (ad es. 2)
  • A3 = estrai_numero(1;4) otterrai uno di questi numeri 1 e 4 ma non 3 e 2 (ad es. 4)
  • A4 = estrai_numero(1;4) otterrai il numero 1
  • A5 = estrai_numero(1;4) otterrai un numero tra 1 e 4, (es. 2) perché la sequenza di prima è finita e ne è stata generata un’altra diversa dalla precedente…

Il codice è il seguente, da inserire in un modulo e che ho commentato a scopo didattico:

'*
'* La funzione estrai_numero restituisce un numero casuale compreso tra
'* un valore minimo e massimo senza restituire più volte lo stesso
'* numero fino alla fine della sequenza numerica. Finita la sequenza
'* generata ne crea una nuova, casuale e diversa dalla precedente e
'* così via.
'*
'* Il codice è a scopo didattico e sei libero di utilizzarlo dove desideri
'* a patto di non rimuovere l'autore originale:
'* (c) Fabio Ottaviani 2009
'* web: https://www.chicercatrova2000.it
'* per segnalare eventuali problemi: debug@chicercatrova2000.it
'*
'* Aggiornato il 15 Novembre 2009
'* Versione 0.1
'*
 

Global deck_array() As Integer
Global indice As Integer
Global ultimo_range As String

Public Function estrai_numero(ByVal n_min As Integer, ByVal n_max As Integer) As String

Dim attuale_range As String

attuale_range = CStr(n_min) & ":" & CStr(n_max)

'*
'* se l'array non è mai stato inizializzato oppure è cambiato il range di valori richiesto
'* oppure sono stati estratti tutti i valori...
'*

If indice >= n_max Or Not (ultimo_range = attuale_range) Then

'*
'* ...lo inizializzo e lo riempio con i valori dal minimo al massimo richiesti...
'*
ReDim deck_array(n_min To n_max)

For n = n_min To n_max

deck_array(n) = n

Next
'*
'* ... poi ne mischio i valori richiamando la funzione mischia(n_volte)...
'*
mischia ((n_max - n_min) * 10)
indice = n_min
ultimo_range = attuale_range

Else
'*
'* ...altrimenti incremento l'indice...
'*
indice = indice + 1

End If
'*
'* ...e restituisco il valore puntato dalla variabile indice.
'*
estrai_numero = CStr(deck_array(indice))

End Function

Private Function mischia(ByVal volte As Integer)

'*
'* Subroutine che esegue la mischiatura dei valori nell'array deck_array() inizializzato.
'* La tecnica utilizzata è quella che sceglie due variabili dell'array a caso
'* e ne scambia i valori contenuti, ripetendo un certo numero di volte l'array
'* risulterà mischiato
'*
Randomize
Dim min As Integer
Dim max As Integer
Dim tmp As Integer
Dim a As Integer
Dim b As Integer

min = LBound(deck_array)
max = UBound(deck_array)

For n = 1 To volte

a = min + Int((max - min + 1) * Rnd())
b = min + Int((max - min + 1) * Rnd())

tmp = deck_array(b)
deck_array(b) = deck_array(a)
deck_array(a) = tmp

Next

deck_array(min) = -deck_array(min) '* Per indicare il primo elemento della sequenza metto in negativo il primo valore

End Function

Per semplicità potete scaricare il foglio di excel con la funzione ed il codice qui. Per renderlo comprensibile il più possibile ho evitato di inserire troppi controlli sugli errori ed ho evitato di perfezionarlo con altre caratteristiche aggiuntive. Le uniche accortezze che ho inserito è che il primo elemento di una sequenza è in negativo così è possibile individuarlo e nel caso si cambino i valori minimo e massimo si generi una nuova sequenza.

Enjoy!