Generatore di numeri casuali che genera solo un numero casuale


765

Ho la seguente funzione:

//Function to get random number
public static int RandomNumber(int min, int max)
{
    Random random = new Random();
    return random.Next(min, max);
}

Come lo chiamo:

byte[] mac = new byte[6];
for (int x = 0; x < 6; ++x)
    mac[x] = (byte)(Misc.RandomNumber((int)0xFFFF, (int)0xFFFFFF) % 256);

Se passo quel ciclo con il debugger durante il runtime ottengo valori diversi (che è quello che voglio). Tuttavia, se inserisco un punto di interruzione due righe sotto quel codice, tutti i membri macdell'array hanno lo stesso valore.

Perché succede?


20
usando new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);non si .Next(0, 256)
ottengono

Questo pacchetto NuGet può essere utile. Fornisce un Rand.Next(int, int)metodo statico che fornisce accesso statico a valori casuali senza bloccare o eseguire il problema di riutilizzo del seme
ChaseMedallion

Risposte:


1042

Ogni volta che lo fai new Random()viene inizializzato usando l'orologio. Ciò significa che in un ciclo stretto si ottiene lo stesso valore molte volte. Dovresti conservare una singola istanza casuale e continuare a usare Next nella stessa istanza.

//Function to get a random number 
private static readonly Random random = new Random(); 
private static readonly object syncLock = new object(); 
public static int RandomNumber(int min, int max)
{
    lock(syncLock) { // synchronize
        return random.Next(min, max);
    }
}

Modifica (vedi commenti): perché abbiamo bisogno di un lockqui?

Fondamentalmente, Nextcambierà lo stato interno Randomdell'istanza. Se lo facciamo allo stesso tempo da più thread, si potrebbe obiettare: "Abbiamo appena fatto il risultato ancora più casuale", ma quello che stiamo effettivamente facendo è potenzialmente rompendo l'implementazione interna, e potremmo anche iniziare a ricevere gli stessi numeri da thread diversi, che potrebbe essere un problema - e potrebbe non esserlo. La garanzia di ciò che accade internamente è il problema più grande, però; poiché Randomfa non fornire alcuna garanzia di filo di sicurezza. Quindi ci sono due approcci validi:

  • Sincronizza in modo da non accedervi contemporaneamente da thread diversi
  • Usa Randomistanze diverse per thread

Entrambi possono andare bene; ma il silenziamento di una singola istanza da più chiamanti contemporaneamente richiede solo problemi.

Il lockraggiunge la prima (e più semplice) di questi approcci; tuttavia, un altro approccio potrebbe essere:

private static readonly ThreadLocal<Random> appRandom
     = new ThreadLocal<Random>(() => new Random());

questo è quindi per thread, quindi non è necessario sincronizzare.


19
Come regola generale, tutti i metodi statici dovrebbero essere protetti da thread, poiché è difficile garantire che più thread non lo chiamino contemporaneamente. Di solito non è necessario rendere i metodi di istanza (cioè non statici) sicuri per i thread.
Marc Gravell

5
@Florin - non c'è alcuna differenza rispetto allo "stack based" tra i due. I campi statici sono altrettanto "stati esterni" e saranno assolutamente condivisi tra i chiamanti. Con le istanze, ci sono buone probabilità che thread diversi abbiano istanze diverse (un modello comune). Con la statica, è garantito che tutti condividano (escluso [ThreadStatic]).
Marc Gravell

2
@gdoron stai ricevendo un errore? Il "blocco" dovrebbe impedire ai fili di inciamparsi l'uno sull'altro qui ...
Marc Gravell

6
@Dan se l'oggetto non viene mai esposto pubblicamente: puoi. Il rischio (molto teorico) è che qualche altro thread lo stia bloccando in modi che non ti aspettavi.
Marc Gravell

3
@smiron È molto probabile che tu stia semplicemente usando anche l'esterno casuale di un lucchetto. Il blocco non impedisce a tutti l'accesso a ciò che si sta bloccando: si assicura solo che due istruzioni di blocco nella stessa istanza non vengano eseguite contemporaneamente. Quindi lock (syncObject)aiuterà solo se tutte le random.Next() chiamate sono presenti anche all'interno lock (syncObject). Se lo scenario che descrivi si verifica anche con un lockuso corretto , è anche molto probabile che si verifichi in uno scenario a thread singolo (ad esempio, Randomè sottilmente rotto).
Luaan,

118

Per facilitare il riutilizzo in tutta l'applicazione, può essere utile una classe statica.

public static class StaticRandom
{
    private static int seed;

    private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>
        (() => new Random(Interlocked.Increment(ref seed)));

    static StaticRandom()
    {
        seed = Environment.TickCount;
    }

    public static Random Instance { get { return threadLocal.Value; } }
}

È possibile utilizzare quindi utilizzare un'istanza casuale statica con codice come

StaticRandom.Instance.Next(1, 100);

62

La soluzione di Mark può essere piuttosto costosa poiché deve essere sincronizzata ogni volta.

Possiamo ovviare alla necessità di sincronizzazione utilizzando il modello di archiviazione specifico del thread:


public class RandomNumber : IRandomNumber
{
    private static readonly Random Global = new Random();
    [ThreadStatic] private static Random _local;

    public int Next(int max)
    {
        var localBuffer = _local;
        if (localBuffer == null) 
        {
            int seed;
            lock(Global) seed = Global.Next();
            localBuffer = new Random(seed);
            _local = localBuffer;
        }
        return localBuffer.Next(max);
    }
}

Misura le due implementazioni e dovresti vedere una differenza significativa.


12
I lucchetti sono molto economici quando non sono contestati ... e anche se contestati mi aspetterei che il codice "fai qualcosa con il numero" riduca il costo del lucchetto negli scenari più interessanti.
Marc Gravell

4
D'accordo, questo risolve il problema del blocco, ma non è ancora una soluzione altamente complicata a un problema banale: che è necessario scrivere '' due '' righe di codice per generare un numero casuale anziché uno. Ne vale davvero la pena salvare la lettura di una semplice riga di codice?
EMP,

4
+1 Usare Randomun'istanza globale aggiuntiva per ottenere il seme è una buona idea. Si noti inoltre che il codice può essere ulteriormente semplificato utilizzando la ThreadLocal<T>classe introdotta in .NET 4 (come Phil ha scritto anche di seguito ).
Groo

40

La mia risposta da qui :

Ribadendo la soluzione giusta :

namespace mySpace
{
    public static class Util
    {
        private static rnd = new Random();
        public static int GetRandom()
        {
            return rnd.Next();
        }
    }
}

Quindi puoi chiamare:

var i = Util.GetRandom();

tutto dappertutto.

Se è strettamente necessario un metodo statico stateless vero per generare numeri casuali, è possibile fare affidamento su a Guid.

public static class Util
{
    public static int GetRandom()
    {
        return Guid.NewGuid().GetHashCode();
    }
}

Sarà un po 'più lento, ma può essere molto più casuale di Random.Next, almeno dalla mia esperienza.

Ma non :

new Random(Guid.NewGuid().GetHashCode()).Next();

La creazione di oggetti non necessari lo renderà più lento, specialmente in un ciclo.

E mai :

new Random().Next();

Non solo è più lento (all'interno di un loop), la sua casualità è ... beh non è davvero buona secondo me ..


10
Non sono d'accordo con il caso Guid. La classe Random implementa una distribuzione uniforme. Che non è il caso in Guid. Lo scopo di Guid è quello di essere unici non distribuiti uniformemente (e la sua implementazione è il più delle volte basata su alcune proprietà hardware / macchina che è l'opposto di ... casualità).
Askolein

4
se non puoi provare l'uniformità della generazione di Guid, allora è sbagliato usarlo come casuale (e l'Hash sarebbe un altro passo dall'uniformità). Allo stesso modo, le collisioni non sono un problema: l'uniformità della collisione lo è. Per quanto riguarda la generazione di Guid che non utilizza più l'hardware, vado a RTFM, mio ​​male (qualche riferimento?)
Askolein

5
Esistono due interpretazioni di "Casuale": 1. mancanza di pattern o 2. mancanza di pattern a seguito di un'evoluzione descritta da una distribuzione di probabilità (2 incluse in 1). Il tuo esempio Guid è corretto nel caso 1, non nel caso 2. Al contrario: la Randomclasse corrisponde al caso 2 (quindi anche al caso 1). È possibile sostituire solo l'utilizzo di Randomdal vostro Guid+Hashse siete non in caso 2. Caso 1 è probabilmente sufficiente per rispondere alla domanda, e poi, il tuo Guid+Hashfunziona bene. Ma non si dice chiaramente (ps: questa uniforme )
Askolein

2
@Askolein Solo per alcuni dati di test, eseguo diversi lotti di entrambi Randome Guid.NewGuid().GetHashCode()tramite Ent ( fourmilab.ch/random ) ed entrambi sono ugualmente casuali. new Random(Guid.NewGuid().GetHashCode())funziona altrettanto bene, come pure l'uso di un "master" sincronizzato Randomper generare seed per "child" Randoms. Naturalmente, dipende da come il tuo sistema genera le Guide - per il mio sistema, sono abbastanza casuali, e su altri potrebbe anche essere cripto-casuali. Quindi Windows o MS SQL sembrano andare bene al giorno d'oggi. Mono e / o mobile potrebbero essere diversi, però.
Luaan,

2
@EdB Come ho detto in precedenza nei commenti, mentre Guid (un numero elevato) è pensato per essere univoco, GetHashCodeil Guid in .NET deriva dalla sua rappresentazione di stringa. L'output è abbastanza casuale per i miei gusti.
nawfal,

27

Preferirei usare la seguente classe per generare numeri casuali:

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);

30
Non sono uno degli elettori negativi, ma nota che il PNRG standard risponde a un'esigenza reale, vale a dire essere in grado di riprodurre ripetutamente una sequenza da un seme noto. A volte il costo puro di un vero RNG crittografico è troppo. E a volte è necessario un crypto RNG. Cavalli per corsi, per così dire.
Marc Gravell

4
Secondo la documentazione questa classe è thread-safe, quindi è qualcosa a suo favore.
Rob Church,

Qual è la probabilità che due stringhe casuali siano la stessa cosa usando quella? Se la stringa ha solo 3 caratteri, suppongo che ciò accadrà con alta probabilità, ma cosa succede se la lunghezza è di 255 caratteri è possibile avere la stessa stringa casuale o è garantito che ciò non possa accadere dall'algoritmo?
Lyubomir Velchev,

16

1) Come diceva Marc Gravell, prova a usare UN generatore casuale. È sempre bello aggiungere questo al costruttore: System.Environment.TickCount.

2) Un consiglio. Supponiamo che tu voglia creare 100 oggetti e supponiamo che ognuno di essi debba avere il proprio generatore casuale (utile se si calcolano CARICHI di numeri casuali in un periodo di tempo molto breve). Se lo facessi in un ciclo (generazione di 100 oggetti), potresti farlo in questo modo (per assicurare la totale casualità):

int inMyRandSeed;

for(int i=0;i<100;i++)
{
   inMyRandSeed = System.Environment.TickCount + i;
   .
   .
   .
   myNewObject = new MyNewObject(inMyRandSeed);  
   .
   .
   .
}

// Usage: Random m_rndGen = new Random(inMyRandSeed);

Saluti.


3
Sposterei System.Environment.TickCount fuori dal ciclo. Se spunta mentre stai ripetendo, avrai due elementi inizializzati sullo stesso seme. Un'altra opzione sarebbe quella di combinare il tickcount in modo diverso (ad es. System.Environment.TickCount << 8 + i)
Dolphin

Se ho capito bene: vuoi dire, potrebbe succedere che "System.Environment.TickCount + i" potrebbe risultare il valore SAME?
sabiland,

EDIT: Naturalmente, non è necessario avere TickCount all'interno del loop. Colpa mia :).
sabiland,

2
Il Random()costruttore predefinito chiama Random(Environment.TickCount)comunque
Alsty,

5

Ogni volta che esegui

Random random = new Random (15);

Non importa se lo esegui milioni di volte, utilizzerai sempre lo stesso seme.

Se usi

Random random = new Random ();

Ottieni una sequenza numerica diversa, se un hacker indovina il seme e il tuo algoritmo è legato alla sicurezza del tuo sistema: l'algoritmo è rotto. Io esegui mult. In questo costruttore il seme viene specificato dall'orologio di sistema e se vengono create più istanze in un periodo di tempo molto breve (millisecondi), è possibile che possano avere lo stesso seme.

Se hai bisogno di numeri casuali sicuri devi usare la classe

System.Security.Cryptography.RNGCryptoServiceProvider

public static int Next(int min, int max)
{
    if(min >= max)
    {
        throw new ArgumentException("Min value is greater or equals than Max value.");
    }
    byte[] intBytes = new byte[4];
    using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        rng.GetNonZeroBytes(intBytes);
    }
    return  min +  Math.Abs(BitConverter.ToInt32(intBytes, 0)) % (max - min + 1);
}

Uso:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. Questo non è vero se non specifichi tu stesso il seme.
LarsTech,

Aggiustato. Grazie Esattamente come dici LarsTech, se viene sempre specificato lo stesso seme, verrà sempre generata la stessa sequenza di numeri casuali. Nella mia risposta mi riferisco al costruttore con parametri se usi sempre lo stesso seme. La classe Casuale genera solo numeri pseudo casuali. Se qualcuno scopre quale seme hai usato nel tuo algoritmo, può compromettere la sicurezza o la casualità del tuo algoritmo. Con la classe RNGCryptoServiceProvider, puoi tranquillamente avere numeri casuali. Ho già corretto, grazie mille per la correzione.
Joma,

0

dichiarare la variabile di classe casuale in questo modo:

    Random r = new Random();
    // ... Get three random numbers.
    //     Here you'll get numbers from 5 to 9
    Console.WriteLine(r.Next(5, 10));

se si desidera ottenere un numero casuale diverso ogni volta dall'elenco, utilizzare

r.Next(StartPoint,EndPoint) //Here end point will not be included

Ogni volta dichiarando Random r = new Random()una volta.


Quando lo chiami new Random()usa l'orologio di sistema, ma se chiami l'intero codice due volte di seguito prima che l'orologio cambi, otterrai lo stesso numero casuale. Questo è il punto centrale delle risposte sopra.
Savage

-1

Ci sono molte soluzioni, qui una: se vuoi solo un numero cancella le lettere e il metodo riceve una lunghezza casuale e il risultato.

public String GenerateRandom(Random oRandom, int iLongitudPin)
{
    String sCharacters = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
    int iLength = sCharacters.Length;
    char cCharacter;
    int iLongitudNuevaCadena = iLongitudPin; 
    String sRandomResult = "";
    for (int i = 0; i < iLongitudNuevaCadena; i++)
    {
        cCharacter = sCharacters[oRandom.Next(iLength)];
        sRandomResult += cCharacter.ToString();
    }
    return (sRandomResult);
}

Il problema di base è sempre lo stesso: stai passando in Randomun'istanza, ma ti aspetti ancora che il chiamante crei un'istanza condivisa. Se il chiamante crea una nuova istanza ogni volta e il codice viene eseguito due volte prima che l'orologio cambi, otterrai lo stesso numero casuale. Quindi questa risposta fa ancora ipotesi che potrebbero essere errate.
Savage

Inoltre, il punto fondamentale di avere un metodo per generare numeri casuali è l'incapsulamento - che il metodo chiamante non deve preoccuparsi dell'implementazione, è solo interessato a recuperare un numero casuale
Savage
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.