2013-02-03 15 views
25

Eventuali duplicati:
Random number generator only generating one random numberpiù numeri casuali sono gli stessi

Una domanda principiante. Ho un programma molto semplice che disegna una linea e voglio randomizzare le posizioni, ma ogni volta che creo una nuova istanza di Random restituisce lo stesso valore. Dov'è il problema? Grazie.

private void Draw() 
{ 
    Random random1 = new Random(); 
    int randomNumber1 = random1.Next(0, 300); 
    Random random2 = new Random(); 
    int randomNumber2 = random2.Next(0, 300); 
    Random random3 = new Random(); 
    int randomNumber3 = random3.Next(0, 300); 
    Random random4 = new Random(); 
    int randomNumber4 = random4.Next(0, 300); 
    System.Drawing.Graphics g = this.CreateGraphics(); 
    Pen green = new Pen(Color.Green, 5); 
    g.DrawLine(green, new Point(randomNumber1, randomNumber2), 
         new Point(randomNumber3, randomNumber4)); 
} 

private void btndraw1_Click(object sender, EventArgs e) 
{ 
    Draw(); 
} 

risposta

48

Il motivo per cui questo accade è che ogni volta che si fa un nuovo Random è inizializzato usando l'orologio. Quindi in un ciclo stretto (o molte chiamate una dopo l'altra) si ottiene lo stesso valore un sacco di volte poiché tutte quelle variabili casuali sono inizializzate con lo stesso seme.

Per risolvere questo: Creare una sola variabile casuale, preferibilmente al di fuori della propria funzione e utilizzare solo quell'istanza.

Random random1 = new Random(); 
private void Draw() 
{ 
    int randomNumber1 = random1.Next(0, 300); 
    int randomNumber2 = random1.Next(0, 300); 
    int randomNumber3 = random1.Next(0, 300); 
    int randomNumber4 = random1.Next(0, 300); 
    System.Drawing.Graphics g = this.CreateGraphics(); 
    Pen green = new Pen(Color.Green, 5); 
    g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4)); 
} 
+3

+1 per il metodo esterno. – keyboardP

9

È sufficiente utilizzare la stessa istanza:

Random random = new Random(); 
int randomNumber1 = random.Next(0, 300); 
int randomNumber2 = random.Next(0, 300); 
//... 

numeri casuali in programmazione non sono realmente casuali; si basano su un unico seme che viene preso e manipolato per generare ciò che sembra essere un insieme di numeri casuali. Utilizzando lo stesso seme si otterrà lo stesso insieme di numeri.

Il costruttore predefinito della classe Random utilizza il numero di millisecondi trascorsi da quando il sistema è stato avviato come seme, quindi è stato utilizzato lo stesso seme.

Non c'è davvero alcun motivo per creare più di una volta l'istanza Random; la singola istanza genererà un insieme casuale di numeri su ciascuna esecuzione del codice.

per dimostrare il mio sopra dichiarazione del seme di default, ho usato riflessione:

// System.Random 
/// <summary>Initializes a new instance of the <see cref="T:System.Random" /> class, using a time-dependent default seed value.</summary> 
public Random() : this(Environment.TickCount) 
{ 
} 

E il Environment.TickCount:

// System.Environment 
/// <summary>Gets the number of milliseconds elapsed since the system started.</summary> 
/// <returns>A 32-bit signed integer containing the amount of time in milliseconds that has passed since the last time the computer was started.</returns> 
/// <filterpriority>1</filterpriority> 
public static extern int TickCount 
{ 
    [SecuritySafeCritical] 
    [MethodImpl(MethodImplOptions.InternalCall)] 
    get; 
} 
+1

Solo ... come sai che 'Random' usa l'epoca Unix? La [documentazione] (http://msdn.microsoft.com/en-us/library/h343ddh9.aspx) afferma solo che '' deriva dall'orologio di sistema 'ma non ha mai menzionato l'effettiva implementazione. –

+0

@Alvin buon punto! È proprio quello che ho sempre pensato **, essendo la cosa più ragionevole secondo me. Usando ILSpy ho scoperto che ero sbagliato e il vero seme è il numero di millisecondi da quando è stato avviato il sistema. –

3

È necessaria solo un'istanza della classe Random.

private void Draw() 
    { 
     Random random1 = new Random(); 
     int randomNumber1 = random1.Next(0, 300); 

     int randomNumber2 = random1.Next(0, 300); 

     int randomNumber3 = random1.Next(0, 300); 

     int randomNumber4 = random1.Next(0, 300); 

     System.Drawing.Graphics g = this.CreateGraphics(); 
     Pen green = new Pen(Color.Green, 5); 
     g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4)); 
    } 


    private void btndraw1_Click(object sender, EventArgs e) 
    { 
     Draw(); 
    } 
3
private static readonly Random Random1 = new Random(); 

    private void Draw() 
    { 

     int randomNumber1 = Random1.Next(0, 300); 
     int randomNumber2 = Random1.Next(0, 300); 
     int randomNumber3 = Random1.Next(0, 300); 
     int randomNumber4 = Random1.Next(0, 300); 
     System.Drawing.Graphics g = this.CreateGraphics(); 
     Pen green = new Pen(Color.Green, 5); 
     g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4)); 
    } 


    private void btndraw1_Click(object sender, EventArgs e) 
    { 
     Draw(); 
    } 
3

Non si deve creare un nuovo oggetto Random per ogni numero. Invece, utilizzare lo stesso oggetto:

Random r = new Random(); 

private void Draw() 
{ 
    // Create 4 random numbers 
    int[] numbers = Enumerable.Range(0, 4).Select(x => r.Next(0, 300)).ToArray(); 

    System.Drawing.Graphics g = this.CreateGraphics(); 
    Pen green = new Pen(Color.Green, 5); 
    g.DrawLine(green, new Point(numbers[0], numbers[1]), 
         new Point(numbers[2], numbers[3])); 
} 
-4

Che casuale classe di .Net bisogno è un valore di seme è possibile utilizzare un valore di data come un seme e che avrebbe funzionato.

private void Draw() 
    { 
     Random random1 = new Random(unchecked((int)DateTime.Now.Ticks << (int)100)); 
     int randomNumber1 = random1.Next(0, 300); 
     Random random2 = new Random(unchecked((int)DateTime.Now.Ticks << (int)200)); 
     int randomNumber2 = random2.Next(0, 300); 
     Random random3 = new Random(unchecked((int)DateTime.Now.Ticks << (int)300)); 
     int randomNumber3 = random3.Next(0, 300); 
     Random random4 = new Random(unchecked((int)DateTime.Now.Ticks << (int)400)); 
     int randomNumber4 = random4.Next(0, 300); 
     System.Drawing.Graphics g = this.CreateGraphics(); 
     Pen green = new Pen(Color.Green, 5); 
     g.DrawLine(green, new Point(randomNumber1, randomNumber2), new Point(randomNumber3, randomNumber4)); 
    } 


private void btndraw1_Click(object sender, EventArgs e) 
{ 
    Draw(); 
} 
+1

Perché hai bisogno di un valore di seme come quello che hai usato? Random() usa qualcosa di simile per impostazione predefinita. –

+0

È vero, ma tendono sempre a generare lo stesso numero di invio, è più sicuro aggiungere un numero di seme univoco che garantisca il numero casuale generato per diversi oggetti di classe casuale. – Nikshep

+0

@Nikshep È ancora una soluzione. Cosa succede se hai diverse funzioni che generano 'Random's? Manterrai un conto? In tal caso, perché non avere comunque una statica casuale? E ti rendi conto che spostare le "zecche" lasciate da 100 a 400 posti è ridicolo, giusto? – antonijn

3

Un generatore di numeri casuali (RNG) in realtà non genera numeri casuali. Invece, utilizza un algoritmo per definire una sequenza di numeri, che sembrano essere casuali. Questa sequenza dipende dal seed che viene eseguito tramite detto algoritmo al momento della creazione di RNG.

Per impostazione predefinita, gli RNG vengono creati utilizzando l'orologio del sistema come seme, poiché l'orologio di solito varia ogni volta che si esegue il programma, rendendo estremamente difficile prevedere la sequenza "casuale".

Nel tuo caso, è molto probabile che l'orologio non sia cambiato tra la creazione di un oggetto casuale e un altro; probabilmente a causa del riordino delle istruzioni interno alla CPU.

Come afferma Blachshma, è meglio creare solo un singolo oggetto casuale e utilizzare solo quello.

public static Random MyRNG = new Random(); // create a single static random object, that you can use across all classes 
private void Draw() 
{ 
    randomNumber1 = MyRNG.Next(0, 300); 
    randomNumber2 = MyRNG.Next(0, 300); 
    // and so forth 
} 

Tenete a mente che qualsiasi istanza di System.Random non sono garantiti per essere thread-safe, il che significa che se si pensa di avere più thread condividono lo stesso oggetto casuale, è necessario bloccarlo.

lock (MyRNG) 
{ 
    randomNumber = MyRNG.Next(0, 300); 
} 

In caso contrario, si potrebbe rompere il tuo oggetto casuale, portando a conseguenti chiamate restituendo solo 0 come risultato.