Generarea de numere aleatorii în limbajul C. Generarea numerelor (pseudo)aleatoare Utilizați o instanță a clasei Random pentru apeluri multiple

Generarea de numere aleatorii în limbajul C.  Generarea numerelor (pseudo)aleatoare Utilizați o instanță a clasei Random pentru apeluri multiple
Generarea de numere aleatorii în limbajul C. Generarea numerelor (pseudo)aleatoare Utilizați o instanță a clasei Random pentru apeluri multiple

Traducerea articolului Random numbers de Jon Skeete, cunoscut pe scară largă în cercuri înguste. M-am oprit la acest articol pentru că la un moment dat am întâlnit și eu problema descrisă în el.

Răsfoirea subiectelor după .NETŞi C# pe site-ul StackOverflow, puteți vedea nenumărate întrebări care menționează cuvântul „aleatoriu”, care, de fapt, ridică aceeași întrebare eternă și „indestructibilă”: de ce generatorul de numere aleatoare System.Random „nu funcționează” și cum să „ reparați-o"" Acest articol este dedicat luării în considerare a acestei probleme și modalităților de a o rezolva.

Enunțarea problemei

Pe StackOverflow, în grupurile de știri și listele de corespondență, toate întrebările pe tema „aleatorie” sună cam așa:
Folosesc Random.Next pentru a genera mai multe numere aleatoare, dar metoda returnează același număr atunci când este apelată de mai multe ori. Numărul se modifică de fiecare dată când se lansează aplicația, dar în cadrul unei singure execuții a programului este constant.

Un exemplu de cod este cam așa:
// Cod prost! Nu folosiți! pentru (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
Deci, ce este în neregulă aici?

Explicaţie

Clasa Random nu este un adevărat generator de numere aleatoare, ea conține un generator pseudo numere aleatorii. Fiecare instanță a clasei Random conține o stare internă, iar atunci când metoda Next (sau NextDouble, sau NextBytes) este apelată, metoda folosește acea stare pentru a returna un număr care va apărea aleatoriu. Starea internă este apoi schimbată, astfel încât data viitoare când este apelat Next, acesta va returna un număr diferit aparent aleatoriu decât cel returnat anterior.

Toate „internele” clasei Random complet determinist. Aceasta înseamnă că dacă luați mai multe instanțe ale clasei Random cu aceeași stare inițială, care este specificată prin parametrul constructor sămânță, și pentru fiecare instanță apelați anumite metode în aceeași ordine și cu aceiași parametri, apoi în final veți obține aceleași rezultate.

Deci, ce este în neregulă cu codul de mai sus? Lucrul rău este că folosim o nouă instanță a clasei Random în fiecare iterație a buclei. Constructorul aleatoriu, care nu ia parametri, ia data și ora curente ca semințe. Iterațiile din buclă se vor „defila” atât de repede încât ora sistemului „nu va avea timp să se schimbe” la finalizarea lor; astfel, toate instanțele de Random vor primi aceeași valoare ca starea lor inițială și, prin urmare, vor returna același număr pseudo-aleatoriu.

Cum să remediez asta?

Există multe soluții la problemă, fiecare cu propriile sale avantaje și dezavantaje. Ne vom uita la câteva dintre ele.
Utilizarea unui generator de numere aleatoare criptografice
.NET conține o clasă abstractă RandomNumberGenerator de la care trebuie să moștenească toate implementările generatoarelor de numere aleatoare criptografice (denumite în continuare cryptoRNGs). .NET conține, de asemenea, una dintre aceste implementări - întâlniți clasa RNGCryptoServiceProvider. Ideea unui cripto-RNG este că, chiar dacă este încă un generator de numere pseudo-aleatoare, oferă o imprevizibilitate destul de puternică a rezultatelor. RNGCryptoServiceProvider utilizează mai multe surse de entropie, care sunt în esență „zgomot” în computerul dvs., iar secvența de numere pe care o generează este foarte greu de prezis. Mai mult, zgomotul „în computer” poate fi folosit nu numai ca stare inițială, ci și între apelurile către numere aleatorii ulterioare; astfel, chiar cunoscând starea actuală a clasei, nu va fi suficient să se calculeze atât următoarele numere care vor fi generate în viitor, cât și cele care au fost generate anterior. De fapt, comportamentul exact depinde de implementare. În plus, Windows poate folosi hardware specializat care este o sursă de „aleatorie adevărată” (de exemplu, un senzor de descompunere a izotopilor radioactivi) pentru a genera numere aleatorii și mai sigure și mai fiabile.

Să comparăm asta cu clasa Random discutată anterior. Să presupunem că ați apelat Random.Next(100) de zece ori și ați salvat rezultatele. Dacă aveți suficientă putere de calcul, puteți, numai pe baza acestor rezultate, să calculați starea inițială (seed) cu care a fost creată instanța Random, să preziceți următoarele rezultate ale apelării Random.Next(100) și chiar să calculați rezultatele pentru apeluri de metodă anterioare. Acest comportament este extrem de inacceptabil dacă utilizați numere aleatorii în scopuri de securitate, financiare etc. Crypto RNG-urile funcționează semnificativ mai lent decât clasa Random, dar generează o secvență de numere, fiecare dintre ele mai independentă și mai imprevizibilă de valorile celorlalți.

În cele mai multe cazuri, performanța slabă nu este o problemă - un API prost este. RandomNumberGenerator este conceput pentru a genera secvențe de octeți - asta este tot. Comparați acest lucru cu metodele clasei Random, unde este posibil să obțineți un număr întreg aleatoriu, un număr fracționar și, de asemenea, un set de octeți. O altă proprietate utilă este capacitatea de a obține un număr aleator într-un interval specificat. Comparați aceste posibilități cu matricea de octeți aleatori pe care le produce RandomNumberGenerator. Puteți corecta situația creând propriul wrapper (wrapper) în jurul RandomNumberGenerator, care va converti octeții aleatori într-un rezultat „convenient”, dar această soluție nu este banală.

Cu toate acestea, în majoritatea cazurilor, „slăbiciunea” clasei Random este în regulă dacă puteți rezolva problema descrisă la începutul articolului. Să vedem ce putem face aici.

Utilizați o instanță a clasei Random pentru mai multe apeluri
Iată, rădăcina soluției problemei este să folosiți o singură instanță de Random atunci când creați mai multe numere aleatoare folosind Random.Next. Și este foarte simplu - vezi cum poți schimba codul de mai sus:
// Acest cod va fi mai bun Random rng = new Random(); pentru (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
Acum, fiecare iterație va avea numere diferite... dar asta nu este tot. Ce se întâmplă dacă numim acest bloc de cod de două ori la rând? Așa este, vom crea două instanțe aleatorii cu aceeași sămânță și vom obține două seturi identice de numere aleatorii. Numerele din fiecare set vor fi diferite, dar aceste seturi vor fi egale între ele.

Există două moduri de a rezolva problema. În primul rând, putem folosi nu o instanță, ci un câmp static care conține o instanță de Random, iar apoi fragmentul de cod de mai sus va crea o singură instanță și o va folosi, apelând-o de câte ori este necesar. În al doilea rând, putem elimina complet crearea unei instanțe aleatorii de acolo, mutând-o „mai sus”, în mod ideal chiar în „partea de sus” a programului, unde va fi creată o singură instanță aleatorie, după care va fi transmisă în toate locurile unde sunt necesare numere aleatorii. Aceasta este o idee grozavă, frumos exprimată prin dependențe, dar va funcționa atâta timp cât vom folosi un singur fir.

Siguranța firului

Clasa Random nu este sigură pentru fire. Având în vedere cât de mult ne place să creăm o singură instanță și să o folosim pe parcursul unui program pe toată durata execuției acestuia (singleton, salut!), lipsa siguranței firului devine o adevărată durere în fund. La urma urmei, dacă folosim o singură instanță simultan în mai multe fire, atunci există posibilitatea ca starea sa internă să fie resetată, iar dacă se întâmplă acest lucru, din acel moment instanța va deveni inutilă.

Din nou, există două moduri de a rezolva problema. Prima cale implică încă utilizarea unei singure instanțe, dar de data aceasta folosind blocarea resurselor printr-un monitor. Pentru a face acest lucru, trebuie să creați un wrapper în jurul Random care va include apelurile la metodele sale într-o declarație de blocare, asigurând acces exclusiv la instanță pentru apelant. Această cale este proastă, deoarece reduce performanța în scenariile cu intensitate de fire.

O altă modalitate, pe care o voi descrie mai jos, este să folosiți o instanță pe fir. Singurul lucru de care trebuie să ne asigurăm este că folosim diferite semințe atunci când creăm instanțe, deci nu putem folosi constructori impliciti. În caz contrar, această cale este relativ simplă.

Furnizor securizat

Din fericire, noua clasă generică ThreadLocal , introdus în .NET 4, face foarte ușor să scrieți furnizori care oferă o instanță pe fir. Trebuie doar să transmiteți un delegat constructorului ThreadLocal, care se va referi la obținerea valorii instanței noastre în sine. În acest caz, am decis să folosesc o singură valoare de semințe, inițialând-o folosind Environment.TickCount (care este modul în care funcționează constructorul Random fără parametri). În continuare, numărul rezultat de căpușe este incrementat de fiecare dată când trebuie să obținem o nouă instanță Random pentru un fir separat.

Clasa prezentată mai jos este complet statică și conține o singură metodă publică (deschisă) GetThreadRandom. Această metodă este făcută mai degrabă o metodă decât o proprietate, în principal pentru comoditate: acest lucru va asigura că toate clasele care au nevoie de o instanță de Random vor depinde de Func. (un delegat care indică către o metodă care nu ia parametri și returnează o valoare de tip Random), și nu din clasa Random în sine. Dacă un tip este destinat să ruleze pe un singur fir, poate apela un delegat pentru a obține o singură instanță de Random și apoi o poate folosi pe tot parcursul; dacă tipul trebuie să funcționeze în scenarii cu mai multe fire, poate apela delegatul de fiecare dată când are nevoie de un generator de numere aleatorii. Clasa de mai jos va crea atâtea instanțe ale clasei Random câte fire există și fiecare instanță va începe de la o valoare inițială diferită. Dacă trebuie să folosim furnizorul de numere aleatoare ca dependență în alte tipuri, putem face acest lucru: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom) . Ei bine, iată codul în sine:
folosind System; folosind System.Threading; clasă publică statică RandomProvider (privată static int seed = Environment.TickCount; private static ThreadLocal randomWrapper = ThreadLocal nou (() => nou Random(Interlocked.Increment(ref seed)));
public static Random GetThreadRandom() ( returnează randomWrapper.Value; ) )

Destul de simplu, nu? Acest lucru se datorează faptului că tot codul are ca scop producerea instanței corecte de Random. Odată ce o instanță este creată și returnată, nu contează ce faceți cu ea în continuare: toate emisiunile ulterioare de instanțe sunt complet independente de cea actuală. Desigur, codul clientului are o lacună pentru utilizarea greșită rău intenționată: poate lua o instanță de Random și o poate transmite altor fire în loc să apeleze RandomProvider-ul nostru pe acele alte fire.

Mai rămâne o problemă: folosim un generator de numere aleatoare slab protejat. După cum am menționat mai devreme, există o versiune mult mai sigură a RNG în RandomNumberGenerator, a cărei implementare este în clasa RNGCryptoServiceProvider. Cu toate acestea, API-ul său este destul de dificil de utilizat în scenarii standard.

Ar fi foarte bine dacă furnizorii RNG din cadru ar avea „surse ale aleatoriei” separate. În acest caz, am putea avea un singur API simplu și convenabil, care ar fi susținut atât de implementarea nesigură, dar rapidă, cât și de cea sigură, dar lentă. Ei bine, nu este rău să visezi. Poate că o funcționalitate similară va apărea în versiunile viitoare ale .NET Framework. Poate că cineva care nu este de la Microsoft va oferi propria implementare a adaptorului. (Din păcate, nu voi fi acea persoană... implementarea corectă a așa ceva este surprinzător de complexă.) De asemenea, vă puteți crea propria clasă derivând din Random și suprascriind metodele Sample și NextBytes, dar nu este clar exact cum ar trebui acestea. munca sau chiar propriul eșantion de implementare poate fi mult mai complex decât pare. Poate data viitoare...

În problemele algoritmice educaționale, nevoia de a genera numere întregi aleatorii este destul de comună. Desigur, le puteți primi de la utilizator, dar pot apărea probleme la completarea matricei cu 100 de numere aleatorii.

Funcția standard de bibliotecă a limbajului C (nu C++) rand() ne vine în ajutor.

int rand(void);

Acesta generează un număr întreg pseudo-aleatoriu în intervalul de valori de la 0 la RAND_MAX. Aceasta din urmă este o constantă care variază în funcție de implementarea limbajului, dar în majoritatea cazurilor este 32767.
Ce se întâmplă dacă avem nevoie de numere aleatorii de la 0 la 9? O modalitate tipică de ieșire din această situație este utilizarea operației de divizare modulo.

Dacă avem nevoie de numere de la 1 (nu 0) la 9, atunci putem adăuga unul...

Ideea este următoarea: generăm un număr aleator de la 0 la 8, iar după adăugarea 1 se transformă într-un număr aleator de la 1 la 9.

Și, în sfârșit, cel mai trist lucru.
Din păcate, funcția rand() generează numere pseudoaleatoare, adică. numere care par aleatorii, dar sunt de fapt o succesiune de valori calculate folosind un algoritm inteligent care ia ca parametru așa-numita bob. Aceste. Numerele generate de funcția rand() vor depinde de valoarea pe care o are boabele în momentul în care este apelată. Iar granulația este întotdeauna setată de compilator la valoarea 1. Cu alte cuvinte, succesiunea de numere va fi pseudo-aleatoare, dar întotdeauna aceeași.
Și nu de asta avem nevoie.

Funcția srand() ajută la corectarea situației.

void srand(unsigned int seed);

Setează bobul egal cu valoarea parametrului cu care a fost apelat. Și succesiunea numerelor va fi, de asemenea, diferită.

Dar problema rămâne. Cum să faci bobul aleatoriu, pentru că totul depinde de el?
O modalitate tipică de ieșire din această situație este utilizarea funcției time().

time_t time(time_t* timer);

De asemenea, este moștenit din limbajul C și, atunci când este apelat cu un pointer nul ca parametru, returnează numărul de secunde care au trecut de la 1 ianuarie 1970. Nu, aceasta nu este o glumă.

Acum putem trece valoarea acestei funcții în funcția srand() (care face o distribuție implicită) și vom avea o grămadă aleatorie minunată.
Și numerele vor fi minunate și nerepetitive.

Pentru a utiliza funcțiile rand() și srand() trebuie să includeți un fișier antet , și pentru a utiliza time() - fișier .

Iată un exemplu complet.

#include
#include
#include

folosind namespace std;

int main()
{
cout<< "10 random numbers (1..100): " << endl;
srand(time(NULL));
for(int i=0;i<10;i++) cout << rand() % 100 + 1 << " ";
cin.get();
întoarce 0;
}

Traducerea articolului Random numbers de Jon Skeete, cunoscut pe scară largă în cercuri înguste. M-am oprit la acest articol pentru că la un moment dat am întâlnit și eu problema descrisă în el.

Răsfoirea subiectelor după .NETŞi C# pe site-ul StackOverflow, puteți vedea nenumărate întrebări care menționează cuvântul „aleatoriu”, care, de fapt, ridică aceeași întrebare eternă și „indestructibilă”: de ce generatorul de numere aleatoare System.Random „nu funcționează” și cum să „ reparați-o"" Acest articol este dedicat luării în considerare a acestei probleme și modalităților de a o rezolva.

Enunțarea problemei

Pe StackOverflow, în grupurile de știri și listele de corespondență, toate întrebările pe tema „aleatorie” sună cam așa:
Folosesc Random.Next pentru a genera mai multe numere aleatoare, dar metoda returnează același număr atunci când este apelată de mai multe ori. Numărul se modifică de fiecare dată când se lansează aplicația, dar în cadrul unei singure execuții a programului este constant.

Un exemplu de cod este cam așa:
// Cod prost! Nu folosiți! pentru (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
Deci, ce este în neregulă aici?

Explicaţie

Clasa Random nu este un adevărat generator de numere aleatoare, ea conține un generator pseudo numere aleatorii. Fiecare instanță a clasei Random conține o stare internă, iar atunci când metoda Next (sau NextDouble, sau NextBytes) este apelată, metoda folosește acea stare pentru a returna un număr care va apărea aleatoriu. Starea internă este apoi schimbată, astfel încât data viitoare când este apelat Next, acesta va returna un număr diferit aparent aleatoriu decât cel returnat anterior.

Toate „internele” clasei Random complet determinist. Aceasta înseamnă că dacă luați mai multe instanțe ale clasei Random cu aceeași stare inițială, care este specificată prin parametrul constructor sămânță, și pentru fiecare instanță apelați anumite metode în aceeași ordine și cu aceiași parametri, apoi în final veți obține aceleași rezultate.

Deci, ce este în neregulă cu codul de mai sus? Lucrul rău este că folosim o nouă instanță a clasei Random în fiecare iterație a buclei. Constructorul aleatoriu, care nu ia parametri, ia data și ora curente ca semințe. Iterațiile din buclă se vor „defila” atât de repede încât ora sistemului „nu va avea timp să se schimbe” la finalizarea lor; astfel, toate instanțele de Random vor primi aceeași valoare ca starea lor inițială și, prin urmare, vor returna același număr pseudo-aleatoriu.

Cum să remediez asta?

Există multe soluții la problemă, fiecare cu propriile sale avantaje și dezavantaje. Ne vom uita la câteva dintre ele.
Utilizarea unui generator de numere aleatoare criptografice
.NET conține o clasă abstractă RandomNumberGenerator de la care trebuie să moștenească toate implementările generatoarelor de numere aleatoare criptografice (denumite în continuare cryptoRNGs). .NET conține, de asemenea, una dintre aceste implementări - întâlniți clasa RNGCryptoServiceProvider. Ideea unui cripto-RNG este că, chiar dacă este încă un generator de numere pseudo-aleatoare, oferă o imprevizibilitate destul de puternică a rezultatelor. RNGCryptoServiceProvider utilizează mai multe surse de entropie, care sunt în esență „zgomot” în computerul dvs., iar secvența de numere pe care o generează este foarte greu de prezis. Mai mult, zgomotul „în computer” poate fi folosit nu numai ca stare inițială, ci și între apelurile către numere aleatorii ulterioare; astfel, chiar cunoscând starea actuală a clasei, nu va fi suficient să se calculeze atât următoarele numere care vor fi generate în viitor, cât și cele care au fost generate anterior. De fapt, comportamentul exact depinde de implementare. În plus, Windows poate folosi hardware specializat care este o sursă de „aleatorie adevărată” (de exemplu, un senzor de descompunere a izotopilor radioactivi) pentru a genera numere aleatorii și mai sigure și mai fiabile.

Să comparăm asta cu clasa Random discutată anterior. Să presupunem că ați apelat Random.Next(100) de zece ori și ați salvat rezultatele. Dacă aveți suficientă putere de calcul, puteți, numai pe baza acestor rezultate, să calculați starea inițială (seed) cu care a fost creată instanța Random, să preziceți următoarele rezultate ale apelării Random.Next(100) și chiar să calculați rezultatele pentru apeluri de metodă anterioare. Acest comportament este extrem de inacceptabil dacă utilizați numere aleatorii în scopuri de securitate, financiare etc. Crypto RNG-urile funcționează semnificativ mai lent decât clasa Random, dar generează o secvență de numere, fiecare dintre ele mai independentă și mai imprevizibilă de valorile celorlalți.

În cele mai multe cazuri, performanța slabă nu este o problemă - un API prost este. RandomNumberGenerator este conceput pentru a genera secvențe de octeți - asta este tot. Comparați acest lucru cu metodele clasei Random, unde este posibil să obțineți un număr întreg aleatoriu, un număr fracționar și, de asemenea, un set de octeți. O altă proprietate utilă este capacitatea de a obține un număr aleator într-un interval specificat. Comparați aceste posibilități cu matricea de octeți aleatori pe care le produce RandomNumberGenerator. Puteți corecta situația creând propriul wrapper (wrapper) în jurul RandomNumberGenerator, care va converti octeții aleatori într-un rezultat „convenient”, dar această soluție nu este banală.

Cu toate acestea, în majoritatea cazurilor, „slăbiciunea” clasei Random este în regulă dacă puteți rezolva problema descrisă la începutul articolului. Să vedem ce putem face aici.

Utilizați o instanță a clasei Random pentru mai multe apeluri
Iată, rădăcina soluției problemei este să folosiți o singură instanță de Random atunci când creați mai multe numere aleatoare folosind Random.Next. Și este foarte simplu - vezi cum poți schimba codul de mai sus:
// Acest cod va fi mai bun Random rng = new Random(); pentru (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
Acum, fiecare iterație va avea numere diferite... dar asta nu este tot. Ce se întâmplă dacă numim acest bloc de cod de două ori la rând? Așa este, vom crea două instanțe aleatorii cu aceeași sămânță și vom obține două seturi identice de numere aleatorii. Numerele din fiecare set vor fi diferite, dar aceste seturi vor fi egale între ele.

Există două moduri de a rezolva problema. În primul rând, putem folosi nu o instanță, ci un câmp static care conține o instanță de Random, iar apoi fragmentul de cod de mai sus va crea o singură instanță și o va folosi, apelând-o de câte ori este necesar. În al doilea rând, putem elimina complet crearea unei instanțe aleatorii de acolo, mutând-o „mai sus”, în mod ideal chiar în „partea de sus” a programului, unde va fi creată o singură instanță aleatorie, după care va fi transmisă în toate locurile unde sunt necesare numere aleatorii. Aceasta este o idee grozavă, frumos exprimată prin dependențe, dar va funcționa atâta timp cât vom folosi un singur fir.

Siguranța firului

Clasa Random nu este sigură pentru fire. Având în vedere cât de mult ne place să creăm o singură instanță și să o folosim pe parcursul unui program pe toată durata execuției acestuia (singleton, salut!), lipsa siguranței firului devine o adevărată durere în fund. La urma urmei, dacă folosim o singură instanță simultan în mai multe fire, atunci există posibilitatea ca starea sa internă să fie resetată, iar dacă se întâmplă acest lucru, din acel moment instanța va deveni inutilă.

Din nou, există două moduri de a rezolva problema. Prima cale implică încă utilizarea unei singure instanțe, dar de data aceasta folosind blocarea resurselor printr-un monitor. Pentru a face acest lucru, trebuie să creați un wrapper în jurul Random care va include apelurile la metodele sale într-o declarație de blocare, asigurând acces exclusiv la instanță pentru apelant. Această cale este proastă, deoarece reduce performanța în scenariile cu intensitate de fire.

O altă modalitate, pe care o voi descrie mai jos, este să folosiți o instanță pe fir. Singurul lucru de care trebuie să ne asigurăm este că folosim diferite semințe atunci când creăm instanțe, deci nu putem folosi constructori impliciti. În caz contrar, această cale este relativ simplă.

Furnizor securizat

Din fericire, noua clasă generică ThreadLocal , introdus în .NET 4, face foarte ușor să scrieți furnizori care oferă o instanță pe fir. Trebuie doar să transmiteți un delegat constructorului ThreadLocal, care se va referi la obținerea valorii instanței noastre în sine. În acest caz, am decis să folosesc o singură valoare de semințe, inițialând-o folosind Environment.TickCount (care este modul în care funcționează constructorul Random fără parametri). În continuare, numărul rezultat de căpușe este incrementat de fiecare dată când trebuie să obținem o nouă instanță Random pentru un fir separat.

Clasa prezentată mai jos este complet statică și conține o singură metodă publică (deschisă) GetThreadRandom. Această metodă este făcută mai degrabă o metodă decât o proprietate, în principal pentru comoditate: acest lucru va asigura că toate clasele care au nevoie de o instanță de Random vor depinde de Func. (un delegat care indică către o metodă care nu ia parametri și returnează o valoare de tip Random), și nu din clasa Random în sine. Dacă un tip este destinat să ruleze pe un singur fir, poate apela un delegat pentru a obține o singură instanță de Random și apoi o poate folosi pe tot parcursul; dacă tipul trebuie să funcționeze în scenarii cu mai multe fire, poate apela delegatul de fiecare dată când are nevoie de un generator de numere aleatorii. Clasa de mai jos va crea atâtea instanțe ale clasei Random câte fire există și fiecare instanță va începe de la o valoare inițială diferită. Dacă trebuie să folosim furnizorul de numere aleatoare ca dependență în alte tipuri, putem face acest lucru: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom) . Ei bine, iată codul în sine:
folosind System; folosind System.Threading; clasă publică statică RandomProvider (privată static int seed = Environment.TickCount; private static ThreadLocal randomWrapper = ThreadLocal nou (() => nou Random(Interlocked.Increment(ref seed)));
public static Random GetThreadRandom() ( returnează randomWrapper.Value; ) )

Destul de simplu, nu? Acest lucru se datorează faptului că tot codul are ca scop producerea instanței corecte de Random. Odată ce o instanță este creată și returnată, nu contează ce faceți cu ea în continuare: toate emisiunile ulterioare de instanțe sunt complet independente de cea actuală. Desigur, codul clientului are o lacună pentru utilizarea greșită rău intenționată: poate lua o instanță de Random și o poate transmite altor fire în loc să apeleze RandomProvider-ul nostru pe acele alte fire.

Mai rămâne o problemă: folosim un generator de numere aleatoare slab protejat. După cum am menționat mai devreme, există o versiune mult mai sigură a RNG în RandomNumberGenerator, a cărei implementare este în clasa RNGCryptoServiceProvider. Cu toate acestea, API-ul său este destul de dificil de utilizat în scenarii standard.

Ar fi foarte bine dacă furnizorii RNG din cadru ar avea „surse ale aleatoriei” separate. În acest caz, am putea avea un singur API simplu și convenabil, care ar fi susținut atât de implementarea nesigură, dar rapidă, cât și de cea sigură, dar lentă. Ei bine, nu este rău să visezi. Poate că o funcționalitate similară va apărea în versiunile viitoare ale .NET Framework. Poate că cineva care nu este de la Microsoft va oferi propria implementare a adaptorului. (Din păcate, nu voi fi acea persoană... implementarea corectă a așa ceva este surprinzător de complexă.) De asemenea, vă puteți crea propria clasă derivând din Random și suprascriind metodele Sample și NextBytes, dar nu este clar exact cum ar trebui acestea. munca sau chiar propriul eșantion de implementare poate fi mult mai complex decât pare. Poate data viitoare...

Vă rugăm să suspendați AdBlock pe acest site.

Uneori poate fi necesar să se genereze numere aleatorii. Un exemplu simplu.

Exemplu: determinarea câștigătorului într-un concurs de repost.

Există o listă de 53 de persoane. Este necesar să alegeți un câștigător dintre ei. Dacă îl alegi singur, s-ar putea să fii acuzat de părtinire. Deci te decizi să scrii un program. Va funcționa după cum urmează. Introduceți numărul de participanți N, după care programul afișează un număr - numărul câștigătorului.

Știi deja cum să obții un număr de la un jucător. Dar cum poți forța un computer să se gândească la un număr aleatoriu? În această lecție veți învăța cum să faceți acest lucru.

funcția rand().

Această funcție returnează un număr întreg aleatoriu în intervalul de la zero la RAND_MAX. RAND_MAX este o constantă C specială care deține valoarea maximă întreagă care poate fi returnată de funcția rand().

Funcția rand() este definită în fișierul antet stdlib.h. Prin urmare, dacă doriți să utilizați rand în programul dvs., nu uitați să includeți acest fișier antet. Constanta RAND_MAX este, de asemenea, definită în acest fișier. Puteți găsi acest fișier pe computer și să-i vedeți semnificația.

Să vedem această caracteristică în acțiune. Să rulăm următorul cod:

Listarea 1.

#include // pentru a folosi funcția printf #include // pentru a utiliza funcția rand int main(void) ( /* generează cinci numere întregi aleatoare */ printf("%d\n", rand()); printf("%d\n", rand()); printf ("%d\n", rand()); printf("%d\n", rand());

Ar trebui să arate cam așa.

Fig.1 Cinci numere aleatoare generate de funcția rand

Dar am dori să obținem numere de la 1 la 53 și nu totul la rând. Iată câteva trucuri pentru a vă ajuta să constrângeți funcția rand().

Limitați numerele aleatoare de sus.

Oricine a așteptat la școală momentul când matematica i-ar fi de folos, pregătește-te. Momentul a sosit. Pentru a limita numerele aleatoare de mai sus, puteți folosi operația de obținere a restului de împărțire, pe care ați învățat-o în ultima lecție. Probabil știți că restul împărțirii cu numerele K este întotdeauna mai mic decât numărul K. De exemplu, împărțirea la 4 poate duce la resturile de 0, 1, 2 și 3. Prin urmare, dacă doriți să limitați numerele aleatoare de sus la numărul K, atunci pur și simplu luați restul împărțirii cu K. Ca aceasta:

Lista 2.

#include #include int main(void) ( /* generează cinci numere întregi aleatoare mai mici de 100 */ printf("%d\n", rand()%100); printf("%d\n", rand()%100); printf ("%d\n", rand()%100); printf("%d\n", rand()%100);


Fig.2 Cinci numere aleatoare mai mici de 100

Limitați numerele de mai jos.

Funcția rand returnează numere aleatoare din interval. Ce se întâmplă dacă avem nevoie doar de numere mai mari decât M (de exemplu, 1000)? Ce ar trebuii să fac? Este simplu. Să adăugăm doar valoarea noastră M la ceea ce a returnat funcția rand. Atunci, dacă funcția returnează 0, răspunsul final va fi M, dacă 2394, atunci răspunsul final va fi M + 2394. Cu această acțiune se pare că deplasăm toate numerele înainte cu M unități.

Setați limitele superioare și inferioare ale funcției rand.

De exemplu, obțineți numere de la 80 la 100. Se pare că trebuie doar să combinați cele două metode de mai sus. Vom obține ceva de genul acesta:

Lista 3.

#include #include int main(void) ( /* generează cinci numere întregi aleatoare mai mari de 80 și mai mici de 100 */ printf("%d\n", 80 + rand()%100); printf("%d\n", 80 + rand ()%100); printf("%d\n", 80 + rand()%100); 100);

Încercați să rulați acest program. Surprins?

Da, această metodă nu va funcționa. Să rulăm acest program manual pentru a vedea dacă am făcut o greșeală. Să presupunem că rand() a returnat numărul 143. Restul când se împarte la 100 este 43. Atunci 80 + 43 = 123. Deci această metodă nu funcționează. Un design similar va produce numere de la 80 la 179.

Să ne descompunem expresia pas cu pas. rand()%100 poate returna numere de la 0 la 99 inclusiv. Aceste. din segment.
Operațiunea + 80 deplasează segmentul nostru cu 80 de unități la dreapta. Primim.
După cum puteți vedea, problema noastră se află în marginea dreaptă a segmentului, acesta este deplasat la dreapta cu 79 de unități. Acesta este numărul nostru original 80 minus 1. Să curățăm lucrurile și să mutăm chenarul din dreapta înapoi: 80 + rand()%(100 - 80 + 1) . Atunci totul ar trebui să funcționeze așa cum trebuie.

În general, dacă trebuie să obținem numere din segment, atunci trebuie să folosim următoarea construcție:
A + rand()%(B-A+1) .

Conform acestei formule, vom rescrie ultimul program:

Lista 4.

#include #include int main(void) ( /* generează cinci numere întregi aleatorii din segmentul */ printf("%d\n", 80 + rand()%(100 - 80 + 1)); printf("%d\n", 80 + rand()%(100 - 79)); printf("%d\n", 80 + rand()%21", 80 + printf()%21); „%d\n”, 80 + rand()%21)

Rezultat:


Fig.3 Numere aleatorii dintr-un interval

Ei bine, acum poți rezolva problema inițială a lecției. Generați un număr dintr-un segment. Sau nu poti?

Dar mai întâi, câteva informații utile. Rulați ultimul program de trei ori la rând și scrieți numerele aleatorii pe care le generează. ai observat?

funcția srand().

Da, de fiecare dată apar aceleași numere identice. „Generator așa-așa!” - spui tu. Și nu vei avea total dreptate. Într-adevăr, aceleași numere sunt generate tot timpul. Dar putem influența acest lucru folosind funcția srand(), care este definită și în fișierul antet stdlib.h. Inițializează generatorul de numere aleatoare cu un număr de bază.

Compilați și rulați acest program de mai multe ori:

Lista 5.

#include #include int main(void) ( srand(2); /* generează cinci numere întregi aleatorii din segmentul */ printf("%d\n", 80 + rand()%(100 - 80 + 1)); printf("% d\n", 80 + rand()%(100 - 79)); printf("%d\n", 80 + rand()%21); printf("%d\n", 80 + rand() %21); printf("%d\n", 80 + rand()%21);

Acum schimbați argumentul funcției srand() cu un alt număr (sper că nu ați uitat ce este un argument al funcției?) și compilați și rulați din nou programul. Secvența numerelor trebuie să se schimbe. De îndată ce schimbăm argumentul în funcția srand, și secvența se schimbă. Nu prea practic, nu? Pentru a schimba secvența, trebuie să recompilați programul. Dacă numai acest număr ar fi introdus acolo automat.

Și se poate face. De exemplu, să folosim funcția time(), care este definită în fișierul antet time.h. Această funcție, dacă NULL este transmis ca argument, returnează numărul de secunde care au trecut de la 1 ianuarie 1970. Iată o privire asupra modului în care se face.

Lista 6.

#include #include #include // pentru a folosi funcția time() int main(void) ( srand(time(NULL)); /* generează cinci numere întregi aleatorii din segment */ printf("%d\n", 80 + rand()%( 100 - 80 + 1)); printf("%d\n", 80 + rand()%(100 - 79)); \n", 80 + rand()%21); printf("%d\n", 80 + rand()%21); )

Vă puteți întreba, ce este NULL? O întrebare rezonabilă. Între timp, vă voi spune care este acest cuvânt special rezervat. Pot spune și ce înseamnă un indicator nul, dar... Acest lucru nu vă oferă nicio informație, așa că vă recomand să nu vă gândiți la asta momentan. Și amintiți-vă doar ca pe un fel de truc inteligent. În lecțiile viitoare ne vom uita la acest lucru mai detaliat.

Foarte des în programe este nevoie de a folosi numere aleatorii - de la completarea unei matrice la criptare. Pentru a obține o secvență de numere aleatoare, limbajul C# are clasa Random. Această clasă oferă doi constructori:

  • aleatoriu()- inițializează o instanță a clasei Random cu o valoare inițială care depinde de ora curentă. După cum se știe, timpul poate fi reprezentat în căpușe- impulsuri de 100 nanosecunde începând cu 1 ianuarie 0001. Iar valoarea timpului în ticks este un număr întreg de 64 de biți, care va fi folosit pentru a inițializa instanța generatorului de numere aleatorii.
  • Aleatoriu (Int32)- inițializează o instanță a clasei Random folosind valoarea inițială specificată. Inițializarea unui generator de numere aleatoare în acest fel poate fi convenabilă la depanarea unui program, deoarece în acest caz vor fi generate aceleași numere „aleatoare” de fiecare dată când programul este lansat.
Metoda principală a acestei clase este metoda Next(), care vă permite să obțineți un număr aleator și are un număr de supraîncărcări:
  • Next() - returnează un număr întreg nenegativ aleatoriu în format Int32.
  • Următorul( Int32)- returnează un număr întreg nenegativ aleatoriu care este mai mic decât valoarea specificată.
  • Următorul( Int32 min, Int32 max)- returnează un număr întreg aleatoriu în intervalul specificat. În acest caz, condiția min trebuie îndeplinită
Și, de asemenea, metode
  • NextBytes( octet)- umple elementele matricei de octeți specificate cu numere aleatorii.
  • NextDouble() - returnează un număr aleator în virgulă mobilă, în intervalul )
    rupe; // potrivire găsită, elementul nu se potrivește
    }
    dacă (j == i)
    { // nu s-a găsit nicio potrivire
    a[i] = num; // salvează elementul
    i++; // trece la elementul următor
    }
    }
    pentru (int i = 0; i< 100; i++)
    {

    dacă (i % 10 == 9)
    Console.WriteLine();
    }
    Consola .ReadKey();
    }
    }
    }

    Cu toate acestea, cu cât este mai aproape de sfârșitul matricei, cu atât trebuie efectuate mai multe generații pentru a obține o valoare care nu se repetă.
    Următorul exemplu afișează numărul de apeluri la metoda Next() pentru a obține fiecare element, precum și numărul total de numere aleatorii generate pentru a umple o matrice de 100 de elemente cu valori care nu se repetă.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53

    folosind System;
    spațiu de nume MyProgram
    {
    Programul clasei
    {
    static void Main(string args)
    {
    Random rnd = new Random();
    int a = int nou; // matrice de elemente
    int count = new int ; // matrice de număr de generații
    a = rnd.Next(0, 101);
    int c = 0; // contor al numărului de generații
    număr = 1; // a este generat o singură dată
    pentru (int i = 1; i< 100;)
    {
    int num = rnd.Next(0, 101);
    c++; // a generat elementul încă o dată
    int j;
    pentru (j = 0; j< i; j++)
    {
    dacă (num == a[j])
    rupe;
    }
    dacă (j == i)
    {
    a[i] = num; i++;
    numără[i] = c; c = 0; // salvează numărul de generații
    }
    }
    // Tipărirea valorilor elementelor
    Consolă .WriteLine( „Valori ale elementelor”);
    pentru (int i = 0; i< 100; i++)
    {
    Consola .Write("(0,4) " , a[i]);
    dacă (i % 10 == 9)
    Console.WriteLine();
    }
    Console.WriteLine();
    // Afișează numărul de generații
    Consolă .WriteLine( „Numărul de generare a elementelor”);
    int suma = 0;
    pentru (int i = 0; i< 100; i++)
    {
    suma += număr[i];
    Consola .Write("(0,4) " , count[i]);
    dacă (i % 10 == 9)
    Console.WriteLine();
    }
    Consolă .WriteLine( „Numărul total de generații - (0)”, suma);
    Consola .ReadKey();
    }
    }
    }

    void Main(șir argumente)
    {
    Random rnd = new Random();
    int a = int nou;
    pentru (int i = 0; i< 100; i++)
    a[i] = i;
    pentru (int i = 0; i< 50; i++)
    {
    int i1 = rnd.Next(0, 100); // primul index
    int i2 = rnd.Next(0, 100); // al doilea indice
    // schimb de valori ale elementelor cu indici i1 și i2
    int temp = a;
    a = a;
    a = temp;
    }
    Consolă .WriteLine( „Valori ale elementelor”);
    pentru (int i = 0; i< 100; i++)
    {
    Consola .Write("(0,4) " , a[i]);
    dacă (i % 10 == 9)
    Console.WriteLine();
    }
    Consola .ReadKey();
    }
    }
    }

    Amestecarea valorilor este mai eficientă dacă intervalul de valori se potrivește (sau este aproape de) numărul de valori, deoarece în acest caz numărul de generare aleatoare de elemente este redus semnificativ.