C++ programozás 12. – Véletlen számok

Véletlen számok, avagy bízzuk a sorsra

Programozás során sokszor előfordul, hogy valamilyen értéket véletlenszerűen kell megadni, vagy fel kell tölteni egy tömböt véletlen számokkal. A véletlen szám generálásának módszere attól függ, hogy egész vagy valós számokat szeretnénk sorsolni. Mielőtt azonban nekikezdenénk tisztázni kell, hogy a véletlen szám sorsoláshoz szükség van bizonyos előre megírt kódokra, melyeket #include-olni kell ahhoz, hogy a feladatot megoldhassuk. A kódunk elején a sorsolás egyik részét végző rand() függvény használatához a következő sort kell beilleszteni a kódunk elejére:

#include <cstdlib>

Egész számok sorsolása

Először meg kell határozni annak az intervallumnak a határait, amelyből az adott számot sorsolni kell:

[0;20]
[10;30]
[-10;10]
[-20;0]
[-40;-20]

Ha egész számokat akarunk sorsolni, akkor ezek a típusok jöhetnek szóba. A jó az egészben az, hogy bármilyen egészeket tartalmazó intervallumra egy általános “képlettel” meg lehet adni a sorsolandó számot. Először meg kell határozni az intervallum alsó és felső határát. Ha ezeket tudjuk, akkor jöhet a sorsolást végző programkód. Ennek általános formája a következő:

rand() % (felső-alsó+1) + alsó;

Bontsuk akkor részekre ezt a kódot. Kezdjük belülről kifelé haladva:

A rand() függvény egy véletlen számokat generáló függvény, mely egy egész számot sorsol ki a [0;32767] intervallumból. Így is írhattam volna, hogy a rand() függvény ilyen értékeket sorsol: 0 <= szám <= 32767. Ezt a számot meg kell szorozni az intervallum méretével, amit minden esetben úgy kapunk, hogy a felső határból kivonjuk az alsót és 1-et hozzáadunk. Az egyik példánál maradva a [0;10] intervallum mérete 11, hiszen 10-0+1 = 11. Miért adunk hozzá egyet? Mert ha csak a két szám különbségét vennénk, akkor az intervallumba a felső határ nem tartozna bele. Miért? Hamarosan kiderül. Ha ezt az intervallum méretet behelyettesítjük a megfelelő helyre egyszerűsödik a képlet: [code lang="cpp"]rand() % intervallum_mérete + alsó;[/code] Láthatod, hogy a rand() függvény által sorsolt egész számot elosztjuk az intervallum méretével maradékos osztással. Ebből mi is következik? Az, hogy a maradékos osztás minden esetben kihagyja az eredményből az osztó értékét. Ha 10-et osztok maradékos osztással 3-mal, akkor az eredmény 0-1-2 lehet. A 3 soha. Ez minden esetben így van, tehát maga az osztó nem lehet benne az eredményben. Ezért adunk hozzá egyet, hogy az intervallum felső határa is benne legyen az eredményben. Ha most nézzük a belső részt, akkor alakul a dolog. Nézzük újra a példákat immár behelyettesítve az eddig tanultakat: [code lang="cpp"] [0;20] rand() % 21 + alsó; [10;30] rand() % 21 + alsó; [-10;10] rand() % 21 + alsó; [-20;0] rand() % 21 + alsó; [-40;-20] rand() % 21 + alsó; [/code] Érdekes módon habár 5 különféle típust adtam meg intervallumra, az intervallumok mérete mégis egyforma. Nincs ezzel semmi gond, mert a véletlen szám sorsolás első lépése a megfelelő méretű sorsolási intervallum meghatározása, ami ismétlésképp: felső-alsó+1

Ha ez megvan, akkor már csak ezt a megfelelő méretű intervallumot kell eltolni a számegyenesen a megfelelő irányba úgy, hogy az intervallum alsó határa a megfelelő kezdőpontban legyen. Ez pusztán csak annyit jelent, hogy az osztás után hozzáadom az intervallum alsó határát (ami negatív érték esetén természetesen kivonást jelent). Zárójelezni nem szükséges, a műveletek sorrendje miatt úgyis az osztást hajtja végre először. Lássuk így a megoldásokat:

[0;20]     rand() % 21; // a nullát nem adom hozzá
[10;30]    rand() % 21 + 10;
[-10;10]   rand() % 21 - 10;
[-20;0]    rand() % 21 - 20;
[-40;-20]  rand() % 21 - 40;

Végül álljon itt pár feladat adott intervallumból történő sorsolás gyakorlásához:

[-55;15] [-40;5] [60;105] [-50;35] [45;95] [50;50] [10;25] [20;105] [80;95] [-30;-25] [40;60] [-20;45] [-10;15] [-20;25] [-45;-20] [-25;75] [-20;15] [-15;95]

Álljon itt akkor egy komplex példa egy kis újdonsággal megfűszerezve. Igaz ugyan, hogy itt egy új szerkezetet, az úgynevezett ciklust is használom, de te csak a véletlen szám sorsolási részeket figyeld meg:

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

int main()
{
    srand(time(0));

    cout << "[-10;20]" << endl;
    for( int i = 0; i < 50; i++ )
    {
        cout << rand() % 31 - 10 << " ";
    }
    cout << endl;

    return 0;
}

A két kiemelt sorról eddig nem volt szó. Az első kiemelésre azért van szükség, hogy működjön a második kiemelés. A C++ rand() függvénye egy úgynevezett pszeudo-random véletlen szám generátorral dolgozik. Ez gyakorlatilag annyit jelent, hogy ha elindítjuk, akkor generál egy véletlen számot. Ha újra használjuk, akkor egy következőt. De ez a számsorozat, amit mondjuk 10 szám generálásakor előállít minden esetben ugyanaz lesz. Próbáld csak ki. Ha generálsz egy véletlen számot, akkor a program következő futásakor ugyanazt kapod. Ha 50 véletlen számot generálsz, mind mondjuk a fenti példaprogramban is láthatod, akkor ez minden programindításkor ugyanaz az 50 szám lesz. Hol itt a véletlen? Erre szolgál a második kiemelés, ami az aktuális gépidővel egy kicsit belekever a sorsolásba, így – mivel az idő minden programfuttatáskor már más értéket mutat – mindig máshogy kever bele, így valóban véletlennek tűnik.

Valós számok sorsolása

Azt már tudjuk, hogy a rand() függvény csak egész számokat sorsol. Hogyan lesz akkor valós véletlen számunk? Nem nagy titok, hogy osztással. Itt azonban nem szabad egész vagy maradékos osztást végezni, hiszen azok egész eredményeket adnak, nekünk pedig pont nem erre van szükségünk. Valós véletlen szám sorsolásakor a következő lépéseket kell végrehajtanunk:

  1. Szükség van egy valós számra a [0;1] intervallumból.
  2. Ezt átalakítjuk a megfelelő méretű intervallumra.
  3. A létrehozott intervallumot eltoljuk a szükséges irányba.

Nézzük akkor lépésenként. Elsőként a [0;1] valós szám előállítása:

(double)rand() / RAND_MAX

Először ugyanabból indulunk ki, hogy kell a rand() által generált egész szám az ismert intervallumból. Rögtön ott van egy fura kód az elején: (double) Ez az úgynevezett típuskényszerítés, angolul typecast. Ez azt jelenti, hogy a közvetlenül utána szereplő értéknek közvetlenül megmondja, hogy milyen típusa legyen. A rand(), ha emlékszel, egy egész számot sorsolt, de az intervallum valós számokat kell majd, hogy tartalmazzon. Ezzel megadtuk, hogy a (double) után lévő szám legyen valós. Minderre azért volt szükség, mert így az utána következő osztás nem két egész számmal történik, hogy az osztás is valós osztás lesz. A RAND_MAX egy beépített változó, amely a rand() által sorsolható legnagyobb értéket rögzíti, vagyis a sorsolhat intervallum felső határát. Maga az osztás végeredményként a [0;1] intervallumból ad egy double, vagyis valós számot, mivel az osztandó legnagyobb értéke a legnagyobb sorsolható szám lesz, amit osztasz az elméletileg legnagyobb sorsolható számmal, így az osztás eredményének maximuma így 1 lehet. A legkisebb természetesen a 0, ha a rand() eredménye 0 lett.

Ha megvan egy ekkora valós szám, akkor átalakítjuk ezt a pici [0;1] intervallumot a kívánt méretűre. Itt egyszerűen csak arról van szó, hogy megszorozzuk az eredményként óhajtott intervallum méretével.

((double)rand() / RAND_MAX) * (intervallum mérete)

Az intervallum mérete nagyon fontos, hogy valós számok esetén nem kell, hogy tartalmazza az ottani +1-et, vagyis itt csak a felső-alsó képlettel dolgozzunk! Ennek megfelelően ez a lépés teljes egészében így néz ki:

((double)rand() / RAND_MAX) * (felső-alsó)

Az utolsó lépés az intervallum eltolása hasonló az egész számokhoz képest, ami negatív alsó határ esetén természetesen kivonás lesz:

((double)rand() / RAND_MAX) * (felső-alsó) + alsó;

Nézzük meg az egész véletlen számokhoz megadott gyakorló feladatokat újra (csak hogy ne kelljen feljebb lapozni):

[-55;15] [-40;5] [60;105] [-50;35] [45;95] [50;50] [10;25] [20;105] [80;95] [-30;-25] [40;60] [-20;45] [-10;15] [-20;25] [-45;-20] [-25;75] [-20;15] [-15;95]

Itt egy komplex példa adott intervallumból valós szám sorsolására:

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

int main()
{
    srand(time(0));

    cout << "[-10;20]" << endl;
    for( int i = 0; i < 50; i++ )
    {
        cout << ((double)rand() / RAND_MAX) * 30 - 10 << " ";
    }
    cout << endl;

    return 0;
}

Következő lecke: Ciklusok

3 Replies to “C++ programozás 12. – Véletlen számok”

  1. Pingback: C++ programozás 14. – Tömbök

  2. Pingback: C++ programozás 11. – Függvények

  3. Pingback: C++ programozás 9. – Feltételvizsgálatok

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez az oldal az Akismet szolgáltatást használja a spam csökkentésére. Ismerje meg a hozzászólás adatainak feldolgozását .