Numeri casuali univoci (non ripetitivi) in O (1)?


179

Vorrei generare numeri casuali univoci tra 0 e 1000 che non si ripetono mai (cioè 6 non si presenta due volte), ma ciò non ricorre a qualcosa come una ricerca O (N) dei valori precedenti per farlo. È possibile?


4
Non è la stessa domanda di stackoverflow.com/questions/158716/…
jk.

2
0 tra 0 e 1000?
Pete Kirkham,

4
Se proibisci qualcosa per un tempo costante (come O(n)nel tempo o nella memoria), molte delle risposte sottostanti sono sbagliate, inclusa la risposta accettata.
jww

Come mescolerai un mazzo di carte?
Colonnello Panic,

9
AVVERTIMENTO! Molte delle risposte fornite di seguito per non produrre sequenze veramente casuali , sono più lente di O (n) o comunque difettose! codinghorror.com/blog/archives/001015.html è una lettura essenziale prima di utilizzarne uno o provare a inventare il tuo!
ivan_pozdeev

Risposte:


247

Inizializza un array di 1001 numeri interi con i valori 0-1000 e imposta una variabile, max, sull'indice max corrente dell'array (a partire da 1000). Scegli un numero casuale, r, tra 0 e max, scambia il numero nella posizione r con il numero nella posizione max e restituisci il numero ora nella posizione max. Diminuisci max di 1 e continua. Quando max è 0, reimpostare max alla dimensione dell'array - 1 e ricominciare senza la necessità di reinizializzare l'array.

Aggiornamento: Anche se ho trovato questo metodo da solo quando ho risposto alla domanda, dopo alcune ricerche mi rendo conto che questa è una versione modificata di Fisher-Yates nota come Durstenfeld-Fisher-Yates o Knuth-Fisher-Yates. Poiché la descrizione può essere un po 'difficile da seguire, ho fornito un esempio di seguito (utilizzando 11 elementi anziché 1001):

L'array inizia con 11 elementi inizializzati nell'array [n] = n, il massimo inizia a 10:

+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
                                ^
                               max    

Ad ogni iterazione, viene selezionato un numero casuale r tra 0 e max, l'array [r] e l'array [max] vengono scambiati, il nuovo array [max] viene restituito e il max viene diminuito:

max = 10, r = 3
           +--------------------+
           v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 9, r = 7
                       +-----+
                       v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 8, r = 1
     +--------------------+
     v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 7, r = 5
                 +-----+
                 v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

...

Dopo 11 iterazioni, tutti i numeri nell'array sono stati selezionati, max == 0 e gli elementi dell'array vengono mescolati:

+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

A questo punto, max può essere resettato a 10 e il processo può continuare.


6
Il post di Jeff sullo shuffling suggerisce che questo non restituirà buoni numeri casuali .. codinghorror.com/blog/archives/001015.html
pro

14
@Peter Rounce: penso di no; questo mi sembra l'algoritmo Fisher Yates, citato anche nel post di Jeff (come il bravo ragazzo).
Brent.Longborough,

3
@robert: volevo solo sottolineare che non produce, come nel nome della domanda, "numeri casuali univoci in O (1)".
Charles,

3
@mikera: D'accordo, anche se tecnicamente se stai usando numeri interi di dimensioni fisse, l'intera lista può essere generata in O (1) (con una costante grande, cioè 2 ^ 32). Inoltre, ai fini pratici, la definizione di "random" è importante: se si desidera davvero utilizzare il pool di entropia del proprio sistema, il limite è il calcolo dei bit casuali anziché i calcoli stessi, e in tal caso n log n è rilevante ancora. Ma nel caso probabile che utilizzerai (l'equivalente di) / dev / urandom anziché / dev / random, tornerai a 'praticamente' O (n).
Charles,

4
Sono un po 'confuso, il fatto che dovresti eseguire Niterazioni (11 in questo esempio) per ottenere il risultato desiderato ogni volta significa O(n)? Poiché è necessario eseguire Niterazioni per ottenere N!combinazioni dallo stesso stato iniziale, altrimenti l'output sarà solo uno degli stati N.
Seph

71

Puoi farlo:

  1. Crea un elenco, 0..1000.
  2. Mischia l'elenco. (Vedi Fisher-Yates shuffle per un buon modo per farlo.)
  3. Restituisce i numeri in ordine dall'elenco mescolato.

Quindi questo non richiede una ricerca di vecchi valori ogni volta, ma richiede comunque O (N) per lo shuffle iniziale. Ma come sottolineato da Nils nei commenti, questo è ammortizzato O (1).


5
@Just Some Guy N = 1000, quindi stai dicendo che è O (N / N) che è O (1)
Guvante

1
Se ogni inserimento nell'array mischiato è un'operazione, quindi dopo aver inserito 1 valore, è possibile ottenere 1 valore casuale. 2 per 2 valori e così via, n per n valori. Sono necessarie n operazioni per generare l'elenco, quindi l'intero algoritmo è O (n). Se hai bisogno di 1.000.000 di valori casuali, ci vorranno 1.000.000 di operazioni
Kibbee

3
Pensaci in questo modo, se fosse un tempo costante, ci vorrebbe la stessa quantità di tempo per 10 numeri casuali che sarebbe per 10 miliardi. Ma a causa del mescolamento che prende O (n), sappiamo che questo non è vero.
Kibbee,

1
Questo in realtà richiede il tempo ammortizzato O (log n), poiché è necessario generare nlg n bit casuali.
Charles,

2
E ora ho tutta la giustificazione per farlo! meta.stackoverflow.com/q/252503/13
Chris Jester-Young

60

Usare un registro turni di feedback lineare massimo .

È implementabile in poche righe di C e in fase di esecuzione fa poco più di un paio di test / rami, una piccola aggiunta e un po 'di spostamento. Non è casuale, ma stupisce la maggior parte delle persone.


12
"Non è casuale, ma stupisce la maggior parte delle persone". Questo vale per tutti i generatori di numeri pseudo-casuali e tutte le risposte possibili a questa domanda. Ma la maggior parte delle persone non ci penserà. Quindi omettere questa nota potrebbe comportare più voti ...
f3lix,

3
@bobobobo: O (1) memoria è il motivo.
Ash,

3
Nit: è memoria O (log N).
Paul Hankin,

2
Usando quel metodo, come si generano i numeri diciamo tra 0 e 800000? Alcuni potrebbero usare un LFSR il cui periodo è 1048575 (2 ^ 20 - 1) e ottenere il prossimo se il numero è fuori portata ma questo non sarà efficiente.
Tigrou,

1
Come LFSR, questo non produce sequenze distribuite uniformemente : l'intera sequenza che verrebbe generata è definita dal primo elemento.
ivan_pozdeev

21

È possibile utilizzare un generatore congruenziale lineare . Dove m(il modulo) sarebbe il primo più vicino maggiore di 1000. Quando ottieni un numero fuori dall'intervallo, prendi solo quello successivo. La sequenza si ripeterà solo dopo che si sono verificati tutti gli elementi e non è necessario utilizzare una tabella. Sii consapevole degli svantaggi di questo generatore (inclusa la mancanza di casualità).


1
1009 è il primo numero primo dopo il 1000.
Teepeemm,

Un LCG ha un'elevata correlazione tra numeri consecutivi, quindi le combinazioni non saranno abbastanza casuali in generale (ad esempio numeri più lontani che kseparati nella sequenza non possono mai avvenire insieme).
ivan_pozdeev

m dovrebbe essere il numero di elementi 1001 (1000 + 1 per zero) e puoi usare Next = (1002 * Current + 757) mod 1001;
Max Abramovich,

21

È possibile utilizzare la crittografia di conservazione del formato per crittografare un contatore. Il tuo contatore va da 0 in su e la crittografia utilizza una chiave di tua scelta per trasformarla in un valore apparentemente casuale di qualsiasi radix e larghezza desideri. Ad esempio per l'esempio in questa domanda: radix 10, larghezza 3.

I cifrari a blocchi hanno normalmente una dimensione di blocco fissa, ad esempio 64 o 128 bit. Ma la crittografia di conservazione del formato ti consente di prendere una cifra standard come AES e creare una cifra di larghezza inferiore, di qualsiasi radix e larghezza desideri, con un algoritmo che è ancora crittograficamente robusto.

È garantito che non si verificheranno mai collisioni (poiché gli algoritmi crittografici creano una mappatura 1: 1). È anche reversibile (una mappatura a 2 vie), quindi puoi prendere il numero risultante e tornare al valore del contatore con cui hai iniziato.

Questa tecnica non richiede memoria per archiviare un array mischiato, ecc., Che può essere un vantaggio su sistemi con memoria limitata.

AES-FFX è un metodo standard proposto per raggiungere questo obiettivo. Ho sperimentato un codice Python di base che si basa sull'idea AES-FFX, anche se non completamente conforme-- vedi il codice Python qui . Può ad esempio crittografare un contatore con un numero decimale di 7 cifre dall'aspetto casuale o un numero di 16 bit. Ecco un esempio di radix 10, larghezza 3 (per dare un numero compreso tra 0 e 999 incluso) come indicato nella domanda:

000   733
001   374
002   882
003   684
004   593
005   578
006   233
007   811
008   072
009   337
010   119
011   103
012   797
013   257
014   932
015   433
...   ...

Per ottenere diverse sequenze pseudo-casuali non ripetitive, modificare la chiave di crittografia. Ogni chiave di crittografia produce una diversa sequenza pseudo-casuale non ripetitiva.


Questa è essenzialmente una semplice mappatura, quindi non diversa da LCG e LFSR, con tutti i nodi rilevanti (ad es. Valori più che kdistanti nella sequenza non possono mai verificarsi insieme).
ivan_pozdeev

@ivan_pozdeev: ho difficoltà a comprendere il significato del tuo commento. Puoi spiegarci cosa c'è di sbagliato in questa mappatura, quali sono "tutti i nodi rilevanti" e che cos'è k?
Craig McQueen,

Tutta la "crittografia" che fa effettivamente qui è sostituire la sequenza 1,2,...,Ncon una sequenza degli stessi numeri in un altro ordine, ma ancora costante. I numeri vengono quindi estratti da questa sequenza uno per uno. kè il numero di valori scelti (l'OP non ha specificato una lettera per questo, quindi ho dovuto introdurne uno).
ivan_pozdeev,

3
@ivan_pozdeev Non è il caso che FPE debba implementare una mappatura statica specifica, o che "la combinazione restituita è completamente definita dal primo numero". Poiché il parametro di configurazione è molto più grande della dimensione del primo numero (che ha solo un migliaio di stati), dovrebbero esserci più sequenze che iniziano con lo stesso valore iniziale e quindi passano a valori successivi diversi. Qualsiasi generatore realistico non riuscirà a coprire l'intero spazio possibile delle permutazioni; non vale la pena alzare quella modalità di fallimento quando l'OP non l'ha richiesta.
sh1

4
+1. Se implementato correttamente, usando un codice a blocchi sicuro con una chiave scelta in modo uniforme in modo casuale, le sequenze generate usando questo metodo saranno indistinguibili dal punto di vista computazionale da un vero shuffle casuale. Vale a dire, non c'è modo di distinguere l'output di questo metodo da un vero shuffle casuale significativamente più veloce che testando tutte le possibili chiavi di cifratura a blocchi e vedendo se qualcuno di essi genera lo stesso output. Per un codice con uno spazio chiave a 128 bit, questo è probabilmente al di là della potenza di calcolo attualmente disponibile per l'umanità; con chiavi a 256 bit, probabilmente rimarrà tale per sempre.
Ilmari Karonen,

7

Per numeri bassi come 0 ... 1000, la creazione di un elenco che contiene tutti i numeri e lo mescola è semplice. Ma se l'insieme di numeri da cui trarre è molto grande, c'è un altro modo elegante: puoi costruire una permutazione pseudocasuale usando un tasto e una funzione di hash crittografica. Vedi il seguente C ++ - pseudo codice esempio ish:

unsigned randperm(string key, unsigned bits, unsigned index) {
  unsigned half1 =  bits    / 2;
  unsigned half2 = (bits+1) / 2;
  unsigned mask1 = (1 << half1) - 1;
  unsigned mask2 = (1 << half2) - 1;
  for (int round=0; round<5; ++round) {
    unsigned temp = (index >> half1);
    temp = (temp << 4) + round;
    index ^= hash( key + "/" + int2str(temp) ) & mask1;
    index = ((index & mask2) << half1) | ((index >> half2) & mask1);
  }
  return index;
}

Qui, hashc'è solo una funzione pseudo casuale arbitraria che mappa una stringa di caratteri a un intero senza segno possibilmente enorme. La funzione randpermè una permutazione di tutti i numeri entro 0 ... pow (2, bit) -1 assumendo una chiave fissa. Ciò deriva dalla costruzione perché ogni passaggio che modifica la variabile indexè reversibile. Questo è ispirato a un codice Feistel .


Come stackoverflow.com/a/16097246/648265 , la casualità delle sequenze non è uguale.
ivan_pozdeev,

1
@ivan_pozdeev: in teoria, ipotizzando una potenza di calcolo infinita, sì. Tuttavia, supponendo che hash(), come utilizzato nel codice sopra, sia una funzione pseudocasuale sicura, questa costruzione produrrà (Luby & Rackoff, 1988) una permutazione pseudocasuale , che non può essere distinta da un vero shuffle casuale usando uno sforzo significativamente minore di un esaustivo ricerca dell'intero spazio chiave, che è esponenziale nella lunghezza della chiave. Anche per chiavi di dimensioni ragionevoli (diciamo 128 bit), questo va oltre la potenza di calcolo totale disponibile sulla Terra.
Ilmari Karonen,

(A proposito, solo per rendere questo argomento un po 'più rigoroso, preferirei sostituire la hash( key + "/" + int2str(temp) )costruzione ad hoc sopra con HMAC , la cui sicurezza a sua volta può essere dimostrata ridotta a quella della funzione di compressione hash sottostante. Inoltre, l'uso di HMAC potrebbe rendere è meno probabile che qualcuno tenti erroneamente di usare questa costruzione con una funzione hash non crittografica non
sicura

6

Puoi usare il mio algoritmo Xincrol descritto qui:

http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html

Questo è un metodo algoritmico puro per generare numeri casuali ma univoci senza array, elenchi, permutazioni o carico pesante della CPU.

L'ultima versione consente anche di impostare l'intervallo di numeri, ad esempio, se voglio numeri casuali univoci nell'intervallo 0-1073741821.

L'ho praticamente usato per

  • Lettore MP3 che riproduce ogni brano in modo casuale, ma solo una volta per album / directory
  • Fotogrammi pixel saggi effetto dissolvenza (veloce e fluido)
  • Creazione di una nebbia "rumore" segreta sull'immagine per firme e marcatori (steganografia)
  • ID oggetto dati per la serializzazione di enormi quantità di oggetti Java tramite database
  • Protezione dei bit di memoria a tripla maggioranza
  • Crittografia indirizzo + valore (ogni byte non è solo crittografato, ma anche spostato in una nuova posizione crittografata nel buffer). Questo fatto ha fatto impazzire i compagni di crittoanalisi :-)
  • Crittografia da testo semplice a semplice come Crypt Text per SMS, e-mail, ecc.
  • My Texas Hold`em Poker Calculator (THC)
  • Molti dei miei giochi per simulazioni, "mischiare", classifica
  • Di Più

È aperto, gratuito. Provaci...


Quel metodo potrebbe funzionare per un valore decimale, ad es. La combinazione di un contatore decimale a 3 cifre per ottenere sempre un risultato decimale a 3 cifre?
Craig McQueen,

Come esempio dell'algoritmo Xorshift , è un LFSR, con tutti i nodi correlati (ad es. Valori più che kdistanti nella sequenza non possono mai verificarsi insieme).
ivan_pozdeev,

5

Non hai nemmeno bisogno di un array per risolvere questo.

Hai bisogno di una maschera di bit e un contatore.

Inizializza il contatore su zero e incrementalo nelle chiamate successive. XOR il contatore con la maschera di bit (selezionata casualmente all'avvio o fissa) per generare un numero psuedorandom. Se non puoi avere numeri che superano 1000, non usare una maschera di bit più larga di 9 bit. (In altre parole, la maschera di bit è un numero intero non superiore a 511.)

Assicurati che quando il contatore supera 1000, lo resetti a zero. In questo momento puoi selezionare un'altra maschera di bit casuale - se vuoi - per produrre lo stesso set di numeri in un ordine diverso.


2
Ciò ingannerebbe meno persone di un LFSR.
Starblue,

Anche la "maschera di bit" entro 512 ... 1023 è OK. Per un po 'più di casualità falsa vedi la mia risposta. :-)
sellibitze,

Essenzialmente equivalente a stackoverflow.com/a/16097246/648265 , fallisce anche la casualità delle sequenze.
ivan_pozdeev

4

Penso che il generatore congruenziale lineare sarebbe la soluzione più semplice.

inserisci qui la descrizione dell'immagine

e ci sono solo 3 restrizioni sui valori a , c e m

  1. m e c sono relativamente primi,
  2. a-1 è divisibile per tutti i fattori primi di m
  3. a-1 è divisibile per 4 se m è divisibile per 4

PS il metodo è già stato menzionato ma il post ha ipotesi errate sui valori costanti. Le costanti di seguito dovrebbero funzionare bene per il tuo caso

Nel tuo caso si può utilizzare a = 1002, c = 757,m = 1001

X = (1002 * X + 757) mod 1001

3

Ecco un po 'di codice che ho scritto che utilizza la logica della prima soluzione. So che questo è "linguaggio agnostico" ma volevo solo presentarlo come esempio in C # nel caso in cui qualcuno fosse alla ricerca di una rapida soluzione pratica.

// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber];      // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber];     // Shuffled Array - not set

// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)                  
{
    OrderedArray[i] = i;
    listBox1.Items.Add(OrderedArray[i]);
}

// Execute the Shuffle                
for (int i = MaxNumber - 1; i > 0; i--)
{
    RandArrayNum = RandomClass.Next(i + 1);         // Save random #
    ShuffledArray[i] = OrderedArray[RandArrayNum];  // Populting the array in reverse
    LastNumInArray = OrderedArray[i];               // Save Last Number in Test array
    PickedNumInArray = OrderedArray[RandArrayNum];  // Save Picked Random #
    OrderedArray[i] = PickedNumInArray;             // The number is now moved to the back end
    OrderedArray[RandArrayNum] = LastNumInArray;    // The picked number is moved into position
}

for (int i = 0; i < MaxNumber; i++)                  
{
    listBox2.Items.Add(ShuffledArray[i]);
}

3

Questo metodo risulta appropriato quando il limite è alto e si desidera generare solo pochi numeri casuali.

#!/usr/bin/perl

($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)

$last = -1;
for $i (0 .. $n-1) {
    $range = $top - $n + $i - $last;
    $r = 1 - rand(1.0)**(1 / ($n - $i));
    $last += int($r * $range + 1);
    print "$last ($r)\n";
}

Si noti che i numeri vengono generati in ordine crescente, ma è possibile mescolarli in seguito.


Dal momento che questo genera combinazioni piuttosto che permutazioni, è più appropriato per stackoverflow.com/questions/2394246/...
ivan_pozdeev

1
Testare mostra questo è orientato verso numeri più bassi: le probabilità misurati per campioni 2M con (top,n)=(100,10)sono: (0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635). Ho testato in Python, quindi lievi differenze matematiche potrebbero svolgere un ruolo qui (mi sono assicurato che tutte le operazioni per il calcolo rsiano in virgola mobile).
ivan_pozdeev,

Sì, affinché questo metodo funzioni correttamente, il limite superiore deve essere molto maggiore del numero di valori da estrarre.
salva il

Non funzionerà "correttamente" anche se "il limite superiore [è] molto più grande del numero di valori" . Le probabilità saranno ancora irregolari, solo con un margine minore.
Ivan_pozdeev il

2

Potresti usare un buon generatore di numeri pseudo-casuali con 10 bit e buttare via da 1001 a 1023 lasciando da 0 a 1000.

Da qui otteniamo il design per un PRNG a 10 bit ..

  • 10 bit, polinomio di feedback x ^ 10 + x ^ 7 + 1 (periodo 1023)

  • usa un Galois LFSR per ottenere il codice veloce


@Phob No, ciò non accadrà, perché un PRNG a 10 bit basato su un registro a spostamento lineare di feedback è in genere realizzato da un costrutto che assume tutti i valori (tranne uno) una volta, prima di tornare al primo valore. In altre parole, selezionerà 1001 esattamente una sola volta durante un ciclo.
Nuoji,

1
@Phob il punto centrale di questa domanda è selezionare ogni numero esattamente una volta. E poi ti lamenti che 1001 non si verificherà due volte di seguito? Un LFSR con una diffusione ottimale attraverserà tutti i numeri nel suo spazio in modo pseudo casuale, quindi riavvierà il ciclo. In altre parole, non viene utilizzato come una normale funzione casuale. Se utilizzato come casuale, in genere utilizziamo solo un sottoinsieme dei bit. Leggi un po 'a riguardo e presto avrà senso.
Nuoji,

1
L'unico problema è che un dato LFSR ha solo una sequenza, dando così una forte correlazione tra i numeri scelti - in particolare, non generando ogni possibile combinazione.
ivan_pozdeev,

2
public static int[] randN(int n, int min, int max)
{
    if (max <= min)
        throw new ArgumentException("Max need to be greater than Min");
    if (max - min < n)
        throw new ArgumentException("Range needs to be longer than N");

    var r = new Random();

    HashSet<int> set = new HashSet<int>();

    while (set.Count < n)
    {
        var i = r.Next(max - min) + min;
        if (!set.Contains(i))
            set.Add(i);
    }

    return set.ToArray();
}

N I numeri casuali non ripetuti avranno una complessità O (n), come richiesto.
Nota: Casuale deve essere statico con la sicurezza della filettatura applicata.


O (n ^ 2), poiché il numero di tentativi è in media proporzionale al numero di elementi selezionati finora.
ivan_pozdeev

Pensaci, se selezioni min = 0 max = 10000000 e N = 5, riprova ~ = 0, non importa quanti selezionati. Ma sì, hai un punto che se max-min è piccolo, o (N) si rompe.
Erez Robinson,

Se N << (max-min) è ancora proporzionale, è solo un coefficiente molto piccolo. E i coefficienti non contano per una stima asintotica.
ivan_pozdeev,

Questo non è O (n). Ogni volta che il set contiene il valore, questo è un loop aggiuntivo.
paparazzo,

2

Diciamo che vuoi ripassare più volte gli elenchi mescolati, senza avere il O(n)ritardo ogni volta che ricominciare a mescolarlo di nuovo, in tal caso possiamo fare questo:

  1. Creare 2 elenchi A e B, da 0 a 1000, occupa 2nspazio.

  2. L'elenco casuale A che utilizza Fisher-Yates richiede ntempo.

  3. Quando si disegna un numero, eseguire Fisher-Yates shuffle in 1 passaggio sull'altro elenco.

  4. Quando il cursore si trova alla fine dell'elenco, passare all'altro elenco.

preprocess

cursor = 0

selector = A
other    = B

shuffle(A)

Disegnare

temp = selector[cursor]

swap(other[cursor], other[random])

if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1

return temp

Non è necessario conservare 2 elenchi - o esaurire un elenco prima di fissare. Fisher-Yates dà risultati uniformemente casuali da qualsiasi stato iniziale. Per ulteriori informazioni, consultare stackoverflow.com/a/158742/648265 .
ivan_pozdeev

@ivan_pozdeev Sì, è lo stesso risultato, ma la mia idea qui è di renderlo ammortizzato O (1) rendendo lo shuffle parte dell'azione di disegno.
Khaled.K,

Non hai capito. Non è necessario ripristinare l'elenco prima di mescolare di nuovo. Il [1,3,4,5,2]mescolamento produrrà lo stesso risultato del mescolamento [1,2,3,4,5].
ivan_pozdeev,

2

La domanda Come si genera in modo efficiente un elenco di K numeri interi non ripetitivi tra 0 e un limite superiore N è collegato come duplicato - e se si desidera qualcosa che è O (1) per numero casuale generato (senza O (n) costo di avvio)) c'è una semplice modifica della risposta accettata.

Crea una mappa vuota non ordinata (una mappa ordinata vuota prenderà O (log k) per elemento) da intero a intero, anziché utilizzare un array inizializzato. Impostare massimo su 1000 se questo è il massimo,

  1. Scegli un numero casuale, r, tra 0 e max.
  2. Assicurarsi che entrambi gli elementi della mappa r e max esistano nella mappa non ordinata. Se non esistono, crearli con un valore uguale al loro indice.
  3. Scambia gli elementi re max
  4. Restituisce max elemento e decrementa massimo di 1 (se max diventa negativo il gioco è fatto).
  5. Torna al passaggio 1.

L'unica differenza rispetto all'utilizzo di un array inizializzato è che l'inizializzazione degli elementi è posticipata / ignorata, ma genererà gli stessi numeri esatti dallo stesso PRNG.


1

Un'altra possibilità:

È possibile utilizzare una serie di flag. E prendi il prossimo quando è già stato scelto.

Ma fai attenzione dopo 1000 chiamate, la funzione non finirà mai, quindi devi fare una protezione.


Questo è O (k ^ 2), che con un numero di passaggi aggiuntivi proporzionale in media al numero di valori selezionati finora.
ivan_pozdeev

1

Ecco alcuni esempi di codice COBOL con cui puoi giocare.
Posso inviarti il ​​file RANDGEN.exe in modo da poter giocare con esso per vedere se lo desideri.

   IDENTIFICATION DIVISION.
   PROGRAM-ID.  RANDGEN as "ConsoleApplication2.RANDGEN".
   AUTHOR.  Myron D Denson.
   DATE-COMPILED.
  * ************************************************************** 
  *  SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
  *    ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
  *    DUPLICATIONS.  (CALL "RANDGEN" USING RANDGEN-AREA.)
  *     
  *  CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
  *    AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA     
  *
  *    FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE. 
  *    RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED 
  *    AND PASSED BACK TO YOU.
  *
  *  RULES TO USE RANDGEN:
  *
  *    RANDOM-NUMBERS-NEEDED > ZERO 
  *     
  *    COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
  *         
  *    RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
  *    WHEN COUNT-OF-ACCESSES IS ALSO = 0 
  *     
  *    RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
  *    (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)       
  *     
  *    YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
  *     THE FIRST TIME YOU USE RANDGEN.
  *     
  *    BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER > ZERO AND 
  *        RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
  *       
  *    YOU CAN LET RANDGEN BUILD A SEED FOR YOU
  *     
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER = ZERO AND 
  *        RANDOM-NUMBER-NEEDED > ZERO  
  *         
  *     TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
  *        A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
  *        RANDOM NUMBERS.
  *        COMPUTE LOW-RANGE =
  *             ((SECONDS * HOURS * MINUTES * MS) / 3).         
  *        A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
  *        AFTER RANDOM-NUMBER-BUILT IS CREATED 
  *        AND IS BETWEEN LOW AND HIGH RANGE
  *        RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
  *               
  * **************************************************************         
   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
   FILE-CONTROL.
   DATA DIVISION.
   FILE SECTION.
   WORKING-STORAGE SECTION.
   01  WORK-AREA.
       05  X2-POWER                     PIC 9      VALUE 2. 
       05  2X2                          PIC 9(12)  VALUE 2 COMP-3.
       05  RANDOM-NUMBER-BUILT          PIC 9(12)  COMP.
       05  FIRST-PART                   PIC 9(12)  COMP.
       05  WORKING-NUMBER               PIC 9(12)  COMP.
       05  LOW-RANGE                    PIC 9(12)  VALUE ZERO.
       05  HIGH-RANGE                   PIC 9(12)  VALUE ZERO.
       05  YOU-PROVIDE-SEED             PIC X      VALUE SPACE.
       05  RUN-AGAIN                    PIC X      VALUE SPACE.
       05  PAUSE-FOR-A-SECOND           PIC X      VALUE SPACE.   
   01  SEED-TIME.
       05  HOURS                        PIC 99.
       05  MINUTES                      PIC 99.
       05  SECONDS                      PIC 99.
       05  MS                           PIC 99. 
  *
  * LINKAGE SECTION.
  *  Not used during testing  
   01  RANDGEN-AREA.
       05  COUNT-OF-ACCESSES            PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBERS-NEEDED        PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBER                PIC 9(12) VALUE ZERO.
       05  RANDOM-MSG                   PIC X(60) VALUE SPACE.
  *    
  * PROCEDURE DIVISION USING RANDGEN-AREA.
  * Not used during testing 
  *  
   PROCEDURE DIVISION.
   100-RANDGEN-EDIT-HOUSEKEEPING.
       MOVE SPACE TO RANDOM-MSG. 
       IF RANDOM-NUMBERS-NEEDED = ZERO
         DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
         ACCEPT RANDOM-NUMBERS-NEEDED.
       IF RANDOM-NUMBERS-NEEDED NOT NUMERIC 
         MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF RANDOM-NUMBERS-NEEDED = ZERO
         MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES NOT NUMERIC
         MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
         MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
           TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
         DISPLAY 'DO YOU WANT TO PROVIDE SEED  Y OR N: '
           NO ADVANCING
           ACCEPT YOU-PROVIDE-SEED.  
       IF RANDOM-NUMBER = ZERO AND
          (YOU-PROVIDE-SEED = 'Y' OR 'y')
         DISPLAY 'ENTER SEED ' NO ADVANCING
         ACCEPT RANDOM-NUMBER. 
       IF RANDOM-NUMBER NOT NUMERIC
         MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
         GO TO 900-EXIT-RANDGEN.
   200-RANDGEN-DATA-HOUSEKEEPING.      
       MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
       IF COUNT-OF-ACCESSES = ZERO
         COMPUTE LOW-RANGE =
                ((SECONDS * HOURS * MINUTES * MS) / 3).
       COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.  
       COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
       MOVE X2-POWER TO 2X2.             
   300-SET-2X2-DIVISOR.
       IF 2X2 < (HIGH-RANGE + 1) 
          COMPUTE 2X2 = 2X2 * X2-POWER
           GO TO 300-SET-2X2-DIVISOR.    
  * *********************************************************         
  *  IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED.    *
  * ********************************************************* 
       IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
          COMPUTE RANDOM-NUMBER-BUILT =
                ((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
       IF COUNT-OF-ACCESSES = ZERO        
         DISPLAY 'SEED TIME ' SEED-TIME 
               ' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT 
               ' LOW-RANGE  ' LOW-RANGE.          
  * *********************************************     
  *    END OF BUILDING A SEED IF YOU WANTED TO  * 
  * *********************************************               
  * ***************************************************
  * THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT  *  
  * ***************************************************   
   400-RANDGEN-FORMULA.
       COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
       DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER 
         REMAINDER RANDOM-NUMBER-BUILT. 
       IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
          RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
         GO TO 600-RANDGEN-CLEANUP.
       GO TO 400-RANDGEN-FORMULA.
  * *********************************************     
  *    GOOD RANDOM NUMBER HAS BEEN BUILT        *               
  * *********************************************
   600-RANDGEN-CLEANUP.
       ADD 1 TO COUNT-OF-ACCESSES.
       COMPUTE RANDOM-NUMBER = 
            RANDOM-NUMBER-BUILT - LOW-RANGE. 
  * *******************************************************
  * THE NEXT 3 LINE OF CODE ARE FOR TESTING  ON CONSOLE   *  
  * *******************************************************
       DISPLAY RANDOM-NUMBER.
       IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
        GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.     
   900-EXIT-RANDGEN.
       IF RANDOM-MSG NOT = SPACE
        DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
        MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER. 
        MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
       DISPLAY 'RUN AGAIN Y OR N '
         NO ADVANCING.
       ACCEPT RUN-AGAIN.
       IF (RUN-AGAIN = 'Y' OR 'y')
         GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.
       ACCEPT PAUSE-FOR-A-SECOND.
       GOBACK.

1
Non ho idea se questo possa effettivamente soddisfare le esigenze dei PO, ma proponiamo un contributo COBOL!
Mac,

1

La maggior parte delle risposte qui non garantisce che non restituiranno lo stesso numero due volte. Ecco una soluzione corretta:

int nrrand(void) {
  static int s = 1;
  static int start = -1;
  do {
    s = (s * 1103515245 + 12345) & 1023;
  } while (s >= 1001);
  if (start < 0) start = s;
  else if (s == start) abort();

  return s;
}

Non sono sicuro che il vincolo sia ben specificato. Si presume che dopo altre 1000 uscite sia consentito ripetere un valore, ma che consente ingenuamente a 0 di seguire immediatamente dopo 0 purché appaiano entrambi alla fine e all'inizio di set di 1000. Viceversa, mentre è possibile mantenere una distanza di 1000 altri valori tra le ripetizioni, in tal modo forza una situazione in cui la sequenza si riproduce esattamente nello stesso modo ogni volta perché non c'è altro valore che si è verificato al di fuori di quel limite.

Ecco un metodo che garantisce sempre almeno altri 500 valori prima di poter ripetere un valore:

int nrrand(void) {
  static int h[1001];
  static int n = -1;

  if (n < 0) {
    int s = 1;
    for (int i = 0; i < 1001; i++) {
      do {
        s = (s * 1103515245 + 12345) & 1023;
      } while (s >= 1001);
      /* If we used `i` rather than `s` then our early results would be poorly distributed. */
      h[i] = s;
    }
    n = 0;
  }

  int i = rand(500);
  if (i != 0) {
      i = (n + i) % 1001;
      int t = h[i];
      h[i] = h[n];
      h[n] = t;
  }
  i = h[n];
  n = (n + 1) % 1001;

  return i;
}

Questo è un LCG, come stackoverflow.com/a/196164/648265 , non casuale per sequenze così come altri nodi correlati allo stesso modo.
ivan_pozdeev,

Il mio @ivan_pozdeev è meglio di un LCG perché garantisce che non restituirà un duplicato sulla 1001a chiamata.
sh1,

1

Quando N è maggiore di 1000 e devi disegnare K campioni casuali, puoi usare un set che contiene i campioni finora. Per ogni sorteggio si utilizza il campionamento del rifiuto , che sarà un'operazione "quasi" O (1), quindi il tempo di esecuzione totale è quasi O (K) con memoria O (N).

Questo algoritmo si scontra quando K è "vicino" a N. Ciò significa che il tempo di esecuzione sarà molto peggio di O (K). Una semplice correzione è di invertire la logica in modo che, per K> N / 2, si tenga traccia di tutti i campioni che non sono stati ancora disegnati. Ogni estrazione rimuove un campione dal set di rifiuto.

L'altro ovvio problema con il campionamento del rifiuto è che si tratta di archiviazione O (N), che è una cattiva notizia se N è tra i miliardi o più. Tuttavia, esiste un algoritmo che risolve quel problema. Questo algoritmo si chiama algoritmo di Vitter dopo il suo inventore. L'algoritmo è descritto qui . L'essenza dell'algoritmo di Vitter è che dopo ogni estrazione, si calcola un salto casuale usando una certa distribuzione che garantisce un campionamento uniforme.


Ragazzi, per favore! Il metodo Fisher-Yates è rotto. Seleziona il primo con probabilità 1 / N e il secondo con probabilità 1 / (N-1)! = 1 / N. Questo è un metodo di campionamento distorto! Hai davvero bisogno dell'algoritmo del Vittter per risolvere il bias.
Emanuel Landeholm,

0

Fisher Yates

for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

In realtà è O (n-1) in quanto è necessario solo uno scambio per gli ultimi due
Questo è C #

public static List<int> FisherYates(int n)
{
    List<int> list = new List<int>(Enumerable.Range(0, n));
    Random rand = new Random();
    int swap;
    int temp;
    for (int i = n - 1; i > 0; i--)
    {
        swap = rand.Next(i + 1);  //.net rand is not inclusive
        if(swap != i)  // it can stay in place - if you force a move it is not a uniform shuffle
        {
            temp = list[i];
            list[i] = list[swap];
            list[swap] = temp;
        }
    }
    return list;
}

C'è già una risposta con questo, ma è abbastanza lungo e non riconosce che puoi fermarti a 1 (non 0)
paparazzo

0

Si prega di vedere la mia risposta a https://stackoverflow.com/a/46807110/8794687

Si tratta di uno degli algoritmi più semplici che hanno tempo medio di complessità O ( s log s ), s denota la dimensione del campione. Ci sono anche alcuni collegamenti lì agli algoritmi della tabella hash la cui complessità è dichiarata O ( s ).


-1

Qualcuno ha pubblicato "la creazione di numeri casuali in Excel". Sto usando questo ideale. Crea una struttura con 2 parti, str.index e str.ran; Per 10 numeri casuali creare una matrice di 10 strutture. Impostare str.index da 0 a 9 e str.ran su un numero casuale diverso.

for(i=0;i<10; ++i) {
      arr[i].index = i;
      arr[i].ran   = rand();
}

Ordinare l'array in base ai valori in arr [i] .ran. Str.index è ora in un ordine casuale. Di seguito è riportato il codice c:

#include <stdio.h>
#include <stdlib.h>

struct RanStr { int index; int ran;};
struct RanStr arr[10];

int sort_function(const void *a, const void *b);

int main(int argc, char *argv[])
{
   int cnt, i;

   //seed(125);

   for(i=0;i<10; ++i)
   {
      arr[i].ran   = rand();
      arr[i].index = i;
      printf("arr[%d] Initial Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   qsort( (void *)arr, 10, sizeof(arr[0]), sort_function);
   printf("\n===================\n");
   for(i=0;i<10; ++i)
   {
      printf("arr[%d] Random  Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   return 0;
}

int sort_function(const void *a, const void *b)
{
   struct RanStr *a1, *b1;

   a1=(struct RanStr *) a;
   b1=(struct RanStr *) b;

   return( a1->ran - b1->ran );
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.