Generarea aleatorie de numere în C. Generarea de secvențe pseudoaleatoare

05.03.2020 Efecte de text

Limbajele de programare au de obicei funcții care vă permit să generați numere aleatorii într-un interval implicit. De fapt, nu numerele aleatoare sunt generate, ci așa-numitele numere pseudoaleatoare; ele arată aleatoriu, dar sunt calculate folosind o formulă foarte specifică. Dar pentru simplitate, mai jos le vom numi în continuare aleatorii.

În limbajul de programare C, puteți obține un număr aleator folosind funcția rand(), care este inclusă în biblioteca standard a limbajului. Această funcție nu acceptă niciun parametru.

Exercita
Scrieți un program care atribuie rezultatul funcției rand() unei variabile întregi. Afișați valoarea variabilei pe ecran.

Funcția rand() returnează un număr întreg între 0 și valoarea atribuită constantei RAND_MAX. Valoarea lui RAND_MAX este specifică sistemului și este definită în fișierul antet stdlib.h. Deci, de exemplu, ar putea fi 32767 (un număr întreg de doi octeți) sau 2147483647 (un număr întreg de patru octeți).

Exercita
Determinați valoarea lui RAND_MAX pe sistemul dvs. Pentru a face acest lucru, nu uitați să includeți fișierul antet stdlib.h în fișierul cu codul sursă.

Codul de mai jos tipărește 50 de numere aleatorii pe ecran:

#include #include main() (car i; pentru (i = 1; i<= 50 ; i++ ) { printf ("%15d" , rand () ) ; if (i % 5 == 0 ) printf ("\n") ; } }

Corpul buclei se mută pe o nouă linie după fiecare cinci numere afișate pe ecran. Pentru a face acest lucru, se folosește o expresie care conține restul împărțirii i la 5, rezultatul este comparat cu 0. Pentru a preveni trecerea la o nouă linie după primul număr, i se atribuie mai întâi unu, nu zero (deoarece 0 este divizibil cu 5 fără rest) .

Exercita
Copiați codul de mai sus. Rulați programul de mai multe ori, observând dacă obțineți rezultate diferite de la o rulare la alta.

Ar fi trebuit să observați că de fiecare dată când rulați programul numerele rămân aceleași. Chiar dacă recompilați programul, rezultatul nu se va schimba. Acest efect se datorează faptului că numărul inițial (de inițializare), care este înlocuit în formula de calcul al primului și al numerelor pseudoaleatoare ulterioare, este întotdeauna același pentru fiecare sistem. Cu toate acestea, această sămânță poate fi schimbată utilizând funcția srand(), căruia îi este transmis orice număr întreg ca parametru. Este clar că dacă specificați un argument specific pentru o funcție, de exemplu, srand(1000) , atunci numerele vor fi, de asemenea, aceleași de la apel la apel al programului. Deși nu la fel ar fi fost fără srand() . Așadar, apare problema, cum să faci argumentul pentru srand() de asemenea aleatoriu? Se dovedește a fi un cerc vicios.

Exercita
Reluați programul care imprimă 50 de numere aleatoare pentru a cere mai întâi utilizatorului orice număr întreg folosind scanf() și transmiteți-l funcției srand().

Utilizatorul programului poate seta el însuși valoarea de inițializare. Dar cel mai adesea aceasta nu este o cale completă de ieșire din situație. Prin urmare, valoarea de inițializare este legată de un proces care are loc în sistemul de operare, de exemplu, de un ceas. Ora (ținând cont nu numai de ora din zi, ci și de dată) nu este niciodată aceeași. Aceasta înseamnă că valoarea pentru srand() convertită într-un număr întreg din ora sistemului va fi diferită.

Ora curentă poate fi găsită folosind funcția time(), al cărei prototip este descris în fișierul time.h. Trecând time() ca parametru NULL, obținem un număr întreg care poate fi transmis către srand() :
srand(time(NULL));

Exercita
Reluați programul astfel încât valoarea de inițializare să depindă de ora sistemului.

Obținerea de numere întregi aleatorii în intervale specificate

Funcția rand() produce un număr aleator între 0 și RAND_MAX. Ce ar trebui să faceți dacă trebuie să primiți numere aleatorii în alte intervale, de exemplu, de la 100 la 999?

În primul rând, să luăm în considerare o situație mai simplă: obțineți numere aleatorii de la 0 la 5. Dacă încercați să împărțiți orice număr întreg la 5, puteți obține atât 0 (când numărul este divizibil cu 5 fără rest), cât și 1, 2, 3, 4. De exemplu, rand() a returnat numărul 283. Aplicând operația de a găsi restul împărțirii cu 5 acestui număr, obținem 3. Adică expresia rand() % 5 dă orice număr din intervalul ? Este logic să presupunem că ar trebui să găsiți restul diviziunii cu 6. În acest caz, următorul raționament ar fi mai alfabetizat: trebuie să găsiți restul divizării după mărimea intervalului. În acest caz, este egal cu șase valori: 0, 1, 2, 3, 4, 5. Pentru a găsi dimensiunea intervalului, trebuie să scădeți minimul acceptabil din maximul permis și să adăugați unul: max - min + 1. Atenție: dacă, de exemplu, aveți nevoie ca maximul specificat în problemă să nu se încadreze în interval, atunci nu este nevoie să adăugați unul, sau unul trebuie să fie scăzut din maxim.

Exercita
Scrieți un program care produce 50 de numere aleatoare de la 0 la 99 inclusiv.

Deci, știm formula pentru obținerea lungimii intervalului: max - min + 1. Dacă trebuie să obțineți un număr de la 6 la 10 inclusiv, atunci lungimea intervalului va fi egală cu 10 - 6 + 1 = 5 . Expresia rand()% 5 va da orice număr de la 0 la 4 inclusiv. Dar avem nevoie de la 6 la 10. În acest caz, este suficient să adăugați 6 la restul aleator rezultat, adică. minim. Cu alte cuvinte, trebuie să efectuați o tură. Valabil pentru exemplul dat:

  • dacă restul a fost 0, atunci adunând 6, obținem 6;
  • restul 1, adăugați 6, obțineți 7;
  • restul 4, adăugați 6, obțineți 10;
  • nu pot fi mai mult de 4 rest.

În acest caz, formula pentru obținerea unui număr aleatoriu într-un interval arată astfel:

Rand() % range_length + offset

unde range_length este calculată ca b - a + 1, offset este valoarea lui a.

Această formulă include și cazurile în care este necesar să se obțină un număr aleator de la 0 la N, adică sunt cazuri speciale ale acesteia.

Exercita
Afișați o serie de numere aleatorii aparținând intervalului de la 100 la 299 inclusiv.

Puteți obține la fel de ușor numere negative aleatorii. Într-adevăr, dacă intervalul este specificat ca [-35, -1], atunci lungimea sa va fi egală cu -1 - (-35) + 1 = 35, ceea ce este adevărat; Expresia pentru obținerea unui număr aleatoriu va arăta astfel:

rand() % 35 - 35

Deci, dacă restul împărțirii este 0, atunci obținem -35, iar dacă 34, atunci -1. Resturile rămase vor da valori în intervalul de la -35 la -1.

Exercita
Afișați o serie de numere aleatoare aparținând intervalului de la -128 la 127 inclusiv.

Obținerea de numere aleatoare reale

Situația cu numerele reale arată oarecum diferită. În primul rând, nu putem obține restul unei diviziuni dacă dividendul sau divizorul este o fracție. În al doilea rând, atunci când calculați lungimea unui interval, nu puteți adăuga unul.

Să explicăm al doilea motiv. Să presupunem că intervalul este specificat ca . Nu constă dintr-un anumit număr de numere (ca în cazul numerelor întregi), ci dintr-un număr nedefinit (s-ar putea spune infinit) de valori, deoarece numerele reale pot fi reprezentate cu diferite grade de precizie. Ulterior, la rotunjire, va exista în continuare șansa de a obține limita maximă a intervalului, așa că pentru a calcula lungimea intervalului, este suficient să scădem minimul din maxim.

Dacă împărțiți numărul aleator real convertit produs de funcția rand() la valoarea constantei RAND_MAX, obțineți un număr aleator real între 0 și 1. Acum, dacă înmulțiți acest număr cu lungimea intervalului, veți obțineți un număr care se află în intervalul de la 0 la valoarea intervalului de lungime. În continuare, dacă adăugați compensarea la limita minimă, numărul se va încadra în siguranță în intervalul necesar. Astfel, formula pentru obținerea unui număr real aleatoriu arată astfel:

(float) rand() / RAND_MAX * (max - min) + min

Exercita
Completați matricea cu numere aleatorii în intervalul de la 0,51 la 1,00. Afișați valoarea elementelor matricei pe ecran.

Numere aleatoare la fel de probabile

Funcția rand() generează orice număr aleator de la 0 la RAND_MAX cu probabilitate egală. Cu alte cuvinte, numărul 100 are aceeași șansă de a apărea ca și numărul 25876.

Pentru a demonstra acest lucru, este suficient să scrieți un program care numără numărul de apariții ale fiecărei valori. Dacă eșantionul (numărul de „subiecți”) este suficient de mare și intervalul (împrăștierea valorilor) este mic, atunci ar trebui să vedem că procentul de apariții ale uneia sau altei valori este aproximativ același cu cel al altora.

#include #include #define N 500 main () ( int i; int arr[ 5 ] = ( 0 ) ; srand (time (NULL) ) ; for (i= 0 ; i< N; i++ ) switch (rand () % 5 ) { case 0 : arr[ 0 ] ++; break ; case 1 : arr[ 1 ] ++; break ; case 2 : arr[ 2 ] ++; break ; case 3 : arr[ 3 ] ++; break ; case 4 : arr[ 4 ] ++; break ; } for (i= 0 ; i < 5 ; i++ ) printf ("%d - %.2f%%\n", i, ((float ) arr[ i] / N) * 100 ) ;

)

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 „nu funcționează” generatorul de numere aleatoare System.Random și cum se „remediază”. ea"" 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 exact cum 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 = nou ThreadLocal (() => 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.

Probleme de proiectare a interfeței

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 complicat 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 poți?

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.

Salutări tuturor celor care au trecut pe aici. Această scurtă notă va conține câteva cuvinte despre generarea numerelor pseudoaleatoare în C/C++. În special, despre cum să lucrați cu cea mai simplă funcție de generare - rand().

funcția rand().

Găsit în biblioteca standard C++ (stdlib.h). Generează și returnează un număr pseudoaleator în intervalul de la 0 la RAND_MAX. Această constantă poate varia în funcție de arhitectura compilatorului sau procesorului, dar este, în general, valoarea maximă a tipului de date int unsigned. Nu acceptă parametri și nu a acceptat niciodată.

Pentru ca generarea să aibă loc, trebuie să setați sămânța folosind o funcție auxiliară din aceeași bibliotecă - srand(). Ia un număr și îl setează ca punct de plecare pentru generarea unui număr aleatoriu. Dacă sămânța nu este setată, atunci de fiecare dată când programul începe, vom primi identic numere aleatorii. Cea mai evidentă soluție este să trimiteți acolo ora curentă a sistemului. Acest lucru se poate face folosind funcția time(NULL). Această funcție se află în biblioteca time.h.

Am rezolvat teoria, am găsit toate funcțiile pentru generarea numerelor aleatoare, acum să le generăm.

Un exemplu de utilizare a funcției de generare a numerelor aleatoare rand().

#include #include #include folosind namespace std; int main() (srand(time(NULL)); for(int i = 0; i< 10; i++) { cout << rand() << endl; } return 0; }

Rand a generat 10 numere aleatorii pentru noi. Puteți verifica dacă sunt aleatorii rulând programul din nou și din nou. Dar acest lucru nu este suficient, așa că trebuie să vorbim despre gamă.

Setați limitele intervalului pentru rand()

Pentru a genera un număr în intervalul de la A la B inclusiv, trebuie să scrieți următoarele:

A + rand() % ((B + 1) - A);

Valorile limită pot fi și negative, ceea ce vă permite să generați un număr aleator negativ, să ne uităm la un exemplu.

#include #include #include folosind namespace std; int main() ( srand(time(NULL)); int A = -2; int B = 8; for(int i = 0; i< 100; i++) { cout << A + rand() % ((B + 1) - A) << endl; } return 0; }

Funcția care generează numere pseudoaleatoare are un prototip în fișierul bibliotecă stdlib.h:

1
2
3
4
5
6

unsigned long int next = 1;
int rand(void)
{
următorul = următorul * 1103515245;
return ((unsigned int )(next / 65536) * 2768);
}


Funcția rand() nu acceptă argumente, dar operează pe următoarea variabilă cu scop global.

Dacă trebuie să generați o secvență în interval , atunci se folosește formula:

Număr = rand()%(M2-M1+1) + M1;

Unde Număr– numărul generat. M2-M1+1– gama completă de reprezentare a numerelor. M1– offset al intervalului specificat relativ la 0; % - restul diviziei.

De exemplu, dacă trebuie să generați o secvență în intervalul [-10;10], atunci apelul funcției va arăta ca

Număr = rand()%(10+10+1)-10

Număr = rand()%(21)-10

Ca urmare a obținerii restului din împărțirea cu 21, avem un număr de la 0 la 20. Scăzând 10 din numărul rezultat, obținem un număr în intervalul dorit [-10;10].

Cu toate acestea, secvența generată de funcția rand() va arăta la fel de fiecare dată când programul este rulat.

Pentru a genera secvențe diferite de fiecare dată când programul este lansat, este necesar să inițializați următoarea variabilă globală cu o altă valoare decât 1. În acest scop, utilizați funcția
void srand (sedinta int nesemnata)
( următorul = sămânță; )

Pentru a vă asigura că inițializarea următoarei este diferită de fiecare dată când programul este lansat, ora curentă este cel mai adesea folosită ca argument de bază.

Exemplu Completați o matrice de 20 de elemente cu numere aleatorii în intervalul de la 0 la 99.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include
#include
#include
#definiți mărimea 20
int main() (
int a;
srand(time(NULL));
pentru (int i = 0; i {
a[i] = rand() % 100;
printf("%d ", a[i]);
}
getchar();
întoarce 0;
}


Rezultatul executiei

Adesea apare sarcina de a aranja un set existent de valori în ordine aleatorie. Un generator de numere pseudoaleatoare este, de asemenea, utilizat în acest scop. Aceasta creează o matrice și o umple cu valori.
Procedura de amestecare în sine este următoarea. Două valori ale indicilor de matrice sunt generate aleatoriu, iar valorile elementelor cu indicii rezultați sunt schimbate. Procedura se repetă de cel puțin N ori, unde N este numărul de elemente ale matricei.
De exemplu, luați în considerare amestecarea a 20 de valori (de la 1 la 20) și repetarea procedurii de 20 de ori.

Implementarea în C

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

#include
#include
#include
#definiți mărimea 20
int main() (
int a;
srand(time(NULL));

pentru (int i = 0; i< SIZE; i++)
{
a[i] = i + 1;
printf("%2d ", a[i]);
}
pentru (int i = 0; i< SIZE; i++)
{
// Generați aleatoriu doi indici de elemente
int ind1 = rand() % 20;
int ind2 = rand() % 20;
// și schimbă elemente cu acești indici
int temp = a;
a = a;
a = temp;
}
printf("\n");

pentru (int i = 0; i< SIZE; i++)
printf("%2d ", a[i]);
getchar();
întoarce 0;
}


Rezultatul executiei


Adesea apare sarcina de a selecta aleatoriu elementele de matrice specificate anterior. Mai mult, este necesar să se asigure absența repetiției în selecția acestor elemente.
Algoritmul pentru această alegere este următorul:

  • Selectăm aleatoriu indexul unui element de matrice
  • Dacă un element cu același index a fost deja selectat, deplasați-vă la dreapta până ajungem la următorul element neselectat. În același timp, ne asigurăm că „mișcarea spre dreapta” nu depășește limitele matricei. Dacă este detectată o depășire a limitelor matricei, începem să vedem elementele matricei de la început.
  • Selectarea unui element
  • Fixarea elementului așa cum este selectat
  • Repetați acești pași pentru toate celelalte elemente

Implementări în C
Ca rezultat, obținem o nouă matrice b, formată dintr-o selecție aleatorie a elementelor matricei a.

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

#include
#include
#include
#definiți mărimea 20
int main() (
int a;
int b; // matrice rezultată
srand(time(NULL));
// Completați matricea cu valori consecutive de la 1 la 20
pentru (int i = 0; i< SIZE; i++)
{
a[i] = i + 1;
printf("%2d ", a[i]);
}

pentru (int i = 0; i< SIZE; i++)
{
int ind = rand() % 20; // selectează un index arbitrar
în timp ce (a == -1) // în timp ce elementul este „selectat”
{
ind++; // se deplasează la dreapta
ind %= 20; // dacă ajungem la marginea dreaptă, revenim la început
}
b[i] = a; // scrie următorul element al tabloului b
a = -1; // marchează elementul de matrice a ca „selectat”
}
printf("\n");
// Afișează tabloul rezultat
pentru (int i = 0; i< SIZE; i++)
printf("%2d ", b[i]);
getchar();
întoarce 0;
}


Rezultatul executiei