կեղծ պատահական թվեր. Պատահական թվեր ստանդարտ C C-ում պատահական թվով 0-ից մինչև 100

Մենք ունենք թվերի հաջորդականություն՝ բաղկացած գրեթե անկախ տարրերից, որոնք ենթարկվում են տվյալ բաշխմանը։ Սովորաբար հավասարաչափ բաշխված:

Դուք կարող եք պատահական թվեր ստեղծել Excel-ում տարբեր ձևերով և ձևերով: Եկեք նայենք դրանցից լավագույններին:

Պատահական թվերի ֆունկցիա Excel-ում

  1. RAND ֆունկցիան վերադարձնում է պատահական միատեսակ բաշխված իրական թիվ: Այն կլինի 1-ից փոքր, 0-ից մեծ կամ հավասար:
  2. RANDBETWEEN ֆունկցիան վերադարձնում է պատահական ամբողջ թիվ:

Դիտարկենք դրանց օգտագործումը օրինակներով:

RAND-ով պատահական թվերի ընտրություն

Այս ֆունկցիան չի պահանջում որևէ արգումենտ (RAND()):

Օրինակ՝ 1-ից 5-ի միջև պատահական իրական թիվ ստեղծելու համար օգտագործեք հետևյալ բանաձևը՝ =RAND()*(5-1)+1:

Վերադարձված պատահական թիվը հավասարաչափ բաշխվում է միջակայքում:

Ամեն անգամ, երբ աշխատաթերթը հաշվարկվում է, կամ աշխատաթերթի որևէ բջիջի արժեքը փոխվում է, նոր պատահական թիվ է վերադարձվում: Եթե ​​ցանկանում եք պահպանել ստեղծված պոպուլյացիան, կարող եք բանաձևը փոխարինել իր արժեքով։

  1. Մենք սեղմում ենք պատահական թվով բջիջի վրա:
  2. Նշեք բանաձևը բանաձևի տողում:
  3. Սեղմեք F9: ԵՎ ՄՏՆԵԼ։

Ստուգենք առաջին նմուշից պատահական թվերի բաշխման միատեսակությունը՝ օգտագործելով բաշխման հիստոգրամը:


Ուղղահայաց արժեքների միջակայքը հաճախականությունն է: Հորիզոնական - «գրպաններ»:



RANDBETWEEN ֆունկցիան

RANDBETWEEN ֆունկցիայի շարահյուսությունն է (ներքևի սահման, վերին սահման): Առաջին փաստարկը պետք է պակաս լինի երկրորդից: Հակառակ դեպքում գործառույթը սխալ կառաջացնի: Ենթադրվում է, որ սահմանները ամբողջ թվեր են: Բանաձևը մերժում է կոտորակային մասը:

Գործառույթի օգտագործման օրինակ.

Պատահական թվեր 0.1 և 0.01 ճշգրտությամբ.

Ինչպես կատարել պատահական թվերի գեներատոր Excel-ում

Կազմենք պատահական թվերի գեներատոր՝ որոշակի միջակայքից արժեքի գեներացմամբ։ Մենք օգտագործում ենք այնպիսի բանաձև, ինչպիսին է՝ =INDEX(A1:A10;INTEGER(RAND()*10)+1):

Կազմենք պատահական թվերի գեներատոր 0-ից 100 միջակայքում՝ 10 քայլով։

Տեքստային արժեքների ցանկից անհրաժեշտ է ընտրել 2 պատահական: Օգտագործելով RAND ֆունկցիան, մենք համեմատում ենք տեքստային արժեքները A1:A7 միջակայքում պատահական թվերի հետ:

Եկեք օգտագործենք INDEX ֆունկցիան՝ սկզբնական ցուցակից երկու պատահական տեքստային արժեք ընտրելու համար:

Ցանկից մեկ պատահական արժեք ընտրելու համար կիրառեք հետևյալ բանաձևը՝ =INDEX(A1:A7,RANDBETWEEN(1,COUNT(A1:A7))):

Նորմալ բաշխման պատահական թվերի գեներատոր

RAND և RANDBETWEEN ֆունկցիաները արտադրում են պատահական թվեր մեկ բաշխմամբ: Նույն հավանականությամբ ցանկացած արժեք կարող է ընկնել պահանջվող միջակայքի ստորին սահմանի մեջ և վերին սահմանի մեջ: Ստացվում է թիրախային արժեքից հսկայական տարածում։

Նորմալ բաշխումը նշանակում է, որ առաջացած թվերի մեծ մասը մոտ է թիրախին: Եկեք շտկենք RANDBETWEEN բանաձևը և ստեղծենք տվյալների զանգված՝ նորմալ բաշխմամբ։

X ապրանքի արժեքը 100 ռուբլի է: Ամբողջ արտադրված խմբաքանակը ենթակա է նորմալ բաշխման: Պատահական փոփոխականը նույնպես հետևում է հավանականության նորմալ բաշխմանը:

Նման պայմաններում միջակայքի միջին արժեքը 100 ռուբլի է: Եկեք ստեղծենք զանգված և կառուցենք 1,5 ռուբլու ստանդարտ շեղումով նորմալ բաշխմամբ գրաֆիկ։

Մենք օգտագործում ենք ֆունկցիան՝ =NORMINV(RAND();100;1.5):

Excel-ը հաշվարկել է, թե որ արժեքներն են գտնվում հավանականությունների միջակայքում: Քանի որ 100 ռուբլի արժողությամբ ապրանք արտադրելու հավանականությունը առավելագույնն է, բանաձևը ցույց է տալիս 100-ի մոտ արժեքներ, քան մնացածը:

Անցնենք դավադրությանը։ Նախ պետք է աղյուսակ ստեղծել կատեգորիաներով: Դա անելու համար մենք զանգվածը բաժանում ենք ժամանակաշրջանների.

Ստացված տվյալների հիման վրա կարող ենք նորմալ բաշխմամբ դիագրամ կազմել։ Արժեքի առանցքը միջակայքում փոփոխականների քանակն է, կատեգորիայի առանցքը՝ ժամանակաշրջանները:

Նեղ շրջանակներում լայնորեն հայտնի պատահական թվեր հոդվածի թարգմանությունը՝ Jon Skeet. Ես կանգ առա այս հոդվածի վրա, քանի որ մի ժամանակ ես ինքս հանդիպեցի դրանում նկարագրված խնդրին:

Թերթիր թեմաները ըստ .NETԵվ C# StackOverflow կայքում դուք կարող եք տեսնել «պատահական» բառի հիշատակման հետ կապված անթիվ հարցեր, որոնցում, փաստորեն, բարձրացվում է նույն հավերժական և «անկոտրում» հարցը՝ ինչու «չի աշխատում» System.Random պատահական թվերի գեներատորը։ և ինչպես «շտկել» այն »: Այս հոդվածը նվիրված է այս խնդրի քննարկմանը և դրա լուծման ուղիներին:

Խնդրի ձևակերպում

StackOverflow-ում, նորությունների խմբերում և փոստային ցուցակներում, «պատահական»-ի մասին բոլոր հարցերը հնչում են այսպես.
Ես օգտագործում եմ Random.Next-ը մի քանի պատահական թվեր ստեղծելու համար, բայց մեթոդը վերադարձնում է նույն համարը մի քանի զանգերի դեպքում: Թիվը փոխվում է ամեն անգամ, երբ դիմումը գործարկվում է, բայց մեկ ծրագրի կատարման ընթացքում այն ​​հաստատուն է:

Որպես օրինակ կոդ տրված է հետևյալը.
// Վատ կոդ: Չեն օգտագործում! համար (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
Այսպիսով, ինչն է սխալ այստեղ:

Բացատրություն

Random դասը իրական պատահական թվերի գեներատոր չէ, այն պարունակում է գեներատոր կեղծպատահական թվեր. Random դասի յուրաքանչյուր օրինակ պարունակում է որոշակի ներքին վիճակ, և երբ կանչվում է Next (կամ NextDouble, կամ NextBytes) մեթոդը, մեթոդն օգտագործում է այս վիճակը՝ վերադարձնելու համար պատահական թվեր: Դրանից հետո ներքին վիճակն այնպես է փոխվում, որ հաջորդ անգամ, երբ հաջորդ մեթոդը կանչվի, այն կվերադարձնի այլ թվացող պատահական թիվ, քան նախկինում վերադարձվածը։

Random դասի աշխատանքի բոլոր «ներսերը»։ լիովին դետերմինիստական. Սա նշանակում է, որ եթե դուք վերցնում եք Random դասի մի քանի օրինակներ նույն սկզբնական վիճակով, որը սահմանված է կոնստրուկտոր պարամետրի միջոցով. սերմ, և յուրաքանչյուր օրինակի համար զանգահարեք որոշակի մեթոդներ նույն հերթականությամբ և նույն պարամետրերով, դուք կստանաք նույն արդյունքները:

Այսպիսով, ինչն է սխալ վերը նշված կոդը: Վատն այն է, որ մենք օգտագործում ենք Random դասի նոր օրինակ օղակի յուրաքանչյուր կրկնության ներսում: Պատահական կոնստրուկտորը, որը ոչ մի պարամետր չի ընդունում, արժեք է ընդունում ընթացիկ ամսաթիվըև ժամանակը որպես սերմ (սկզբնական վիճակ): Շրջանակի կրկնությունները այնքան արագ են «պտտվելու», որ համակարգի ժամանակը«ժամանակ չունի փոխելու» դրանց վերջում. Այսպիսով, Random-ի բոլոր օրինակները կստանան նույն արժեքը, ինչ իրենց սկզբնական վիճակը և, հետևաբար, կվերադարձնեն նույն կեղծ պատահական թիվը:

Ինչպե՞ս ուղղել այն:

Խնդրի լուծման բազմաթիվ տարբերակներ կան, որոնցից յուրաքանչյուրն ունի իր դրական և բացասական կողմերը: Մենք կանդրադառնանք դրանցից մի քանիսին:
Օգտագործելով գաղտնագրված պատահական թվերի գեներատոր
.NET-ը պարունակում է RandomNumberGenerator աբստրակտ դաս, որից պետք է ժառանգեն գաղտնագրված պատահական թվերի գեներատորների բոլոր իրականացումները (այսուհետ՝ կրիպտո RNG): .NET-ը պարունակում է նաև այս իրականացումներից մեկը՝ հանդիպել RNGCryptoServiceProvider դասին: CryptoRNG-ի գաղափարն այն է, որ նույնիսկ եթե այն դեռևս կեղծ պատահական թվերի գեներատոր է, այն ապահովում է արդյունքների բավականին ուժեղ անկանխատեսելիություն: RNGCryptoServiceProvider-ն օգտագործում է էնտրոպիայի մի քանի աղբյուրներ, որոնք արդյունավետորեն «աղմուկ» են ձեր համակարգչում, և նրա ստեղծած թվերի հաջորդականությունը շատ դժվար է կանխատեսել: Ավելին, «համակարգչային» աղմուկը կարող է օգտագործվել ոչ միայն որպես սկզբնական վիճակ, այլև հաջորդ պատահական համարների զանգերի միջև. Այսպիսով, նույնիսկ իմանալով դասի ներկա վիճակը, դա բավարար չի լինի հաշվարկելու և՛ հաջորդ թվերը, որոնք կստեղծվեն ապագայում, և՛ նրանք, որոնք ստեղծվել են ավելի վաղ: Իրականում ճշգրիտ վարքագիծը կախված է իրականացումից: Բացի այդ, Windows-ը կարող է օգտագործել մասնագիտացված Սարքավորումներ, որը «իսկական պատահականության» աղբյուր է (օրինակ, դա կարող է լինել ռադիոակտիվ իզոտոպների քայքայման դետեկտոր)՝ էլ ավելի ապահով և հուսալի պատահական թվեր ստեղծելու համար։

Եկեք համեմատենք սա նախկինում քննարկված Random դասի հետ: Ենթադրենք, դուք զանգել եք Random.Next(100) տասը անգամ և պահպանել արդյունքները: Եթե ​​ունեք բավարար հաշվողական հզորություն, կարող եք հաշվարկել նախնական վիճակը (seed), որով Random օրինակը ստեղծվել է զուտ այս արդյունքների հիման վրա, կանխատեսել Random.Next(100) կանչելու հաջորդ արդյունքները և նույնիսկ հաշվարկել նախորդ մեթոդի կանչերի արդյունքները: . Այս պահվածքը աղետալիորեն անընդունելի է, եթե դուք օգտագործում եք պատահական թվեր անվտանգության, ֆինանսական նպատակներով և այլն: Crypto RNG-ները զգալիորեն ավելի դանդաղ են աշխատում, քան Random դասը, բայց առաջացնում են թվերի հաջորդականություն, որոնցից յուրաքանչյուրն ավելի անկախ և անկանխատեսելի է մյուսների արժեքներից:

Շատ դեպքերում դանդաղ կատարումը խոչընդոտ չէ, դա վատ API է: RandomNumberGenerator-ը նախատեսված է բայթերի հաջորդականություններ ստեղծելու համար, և վերջ: Համեմատեք սա Random դասի մեթոդների հետ, որտեղ հնարավոր է ստանալ պատահական ամբողջ թիվ, կոտորակային թիվ, ինչպես նաև բայթերի հավաքածու։ Մեկ այլ օգտակար հատկություն- նշված տիրույթում պատահական թիվ ստանալու հնարավորությունը: Համեմատեք այս հնարավորությունները պատահական բայթերի զանգվածի հետ, որն արտադրում է RandomNumberGenerator-ը: Դուք կարող եք շտկել իրավիճակը՝ ստեղծելով ձեր սեփական փաթաթան RandomNumberGenerator-ի շուրջ, որը պատահական բայթերը կվերածի «հարմար» արդյունքի, սակայն այս լուծումը մանրուք չէ:

Այնուամենայնիվ, շատ դեպքերում Random դասի «թուլությունը» լավ է, եթե կարողանաք լուծել հոդվածի սկզբում նկարագրված խնդիրը: Եկեք տեսնենք, թե ինչ կարելի է անել այստեղ:

Օգտագործեք Random դասի մեկ օրինակ մի քանի զանգերի ժամանակ
Ահա, խնդրի լուծման արմատը Random-ի միայն մեկ օրինակ օգտագործելն է՝ Random.Next-ի միջոցով բազմաթիվ պատահական թվեր ստեղծելիս: Եվ դա շատ պարզ է՝ տեսեք, թե ինչպես կարող եք փոխել վերը նշված կոդը.
// Այս կոդը ավելի լավ կլինի Random rng = new Random(); համար (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
Այժմ յուրաքանչյուր կրկնություն կունենա տարբեր թվեր... բայց դա դեռ ամենը չէ: Ի՞նչ կպատահի, եթե մենք երկու անգամ անընդմեջ կանչենք կոդի այս բլոկը: Ճիշտ է, մենք կստեղծենք Random-ի երկու օրինակ՝ նույն սկզբնական արժեքով և կավարտենք պատահական թվերի երկու նույնական խմբերով: Յուրաքանչյուր հավաքածուում թվերը կտարբերվեն, բայց այդ հավաքածուները միմյանց միջև հավասար կլինեն:

Խնդիրը լուծելու երկու ճանապարհ կա. Նախ, մենք կարող ենք օգտագործել ոչ թե օրինակ, այլ ստատիկ դաշտ, որը պարունակում է Random-ի օրինակ, այնուհետև վերը նշված կոդի կտորը կստեղծի միայն մեկ օրինակ և կօգտագործի այն՝ կանչվելով այնքան, որքան ցանկանում ենք: Երկրորդ, մենք կարող ենք ընդհանրապես հեռացնել Random-ի օրինակի ստեղծումը այնտեղից՝ տեղափոխելով այն «ավելի բարձր», իդեալական՝ ծրագրի հենց «վերև», որտեղ կստեղծվի Random-ի մեկ օրինակ, որից հետո այն կտեղափոխվի: դեպի բոլոր այն վայրերը, որտեղ պատահական թվեր են անհրաժեշտ: Դա հիանալի գաղափար է և լավ արտահայտված կախվածություններով, բայց այն կաշխատի այնքան ժամանակ, քանի դեռ մենք օգտագործում ենք միայն մեկ թեմա:

թելի անվտանգություն

Պատահական դասը շղթայից անվտանգ չէ: Հաշվի առնելով, թե ինչպես ենք մենք սիրում ստեղծել մեկ օրինակ և օգտագործել այն ծրագրի ողջ ընթացքում դրա կատարման ընթացքում (միայնակ, բարև), թելի անվտանգության բացակայությունը դառնում է իսկական փուշ: Ի վերջո, եթե մենք օգտագործում ենք մեկ օրինակ միաժամանակ մի քանի թելերում, ապա դրա ներքին վիճակի վերակայման հնարավորություն կա, և եթե դա տեղի ունենա, ապա այդ պահից ինստանցիան անպիտան կդառնա։

Կրկին խնդիրը լուծելու երկու ճանապարհ կա. Առաջին ուղին դեռևս ենթադրում է մեկ օրինակ, բայց այս անգամ օգտագործելով ռեսուրսի վրա մոնիտորի կողպումը: Դա անելու համար դուք պետք է ստեղծեք Random-ի շուրջ փաթաթված, որը կփաթաթի զանգը դեպի իր մեթոդները կողպման հայտարարության մեջ՝ ապահովելով բացառիկ մուտք դեպի օրինակ: Այս ճանապարհը վատ է նրանով, որ նվազեցնում է կատարումը բազմաշերտ ինտենսիվ սցենարներում:

Մեկ այլ եղանակ, որը ես կներկայացնեմ ստորև, յուրաքանչյուր շղթայի համար մեկ օրինակ օգտագործելն է: Միակ բանը, որ մենք պետք է համոզվենք, որ մենք օգտագործում ենք տարբեր սկզբնական արժեքներ (seed) օրինակներ ստեղծելիս, և, հետևաբար, մենք չենք կարող օգտագործել լռելյայն կոնստրուկտորներ: Մնացած բոլոր առումներով այս ճանապարհը համեմատաբար պարզ է:

Ապահով մատակարար

Բարեբախտաբար, ThreadLocal-ի նոր ընդհանուր դասը .NET 4-ում ներկայացված .NET 4-ում շատ հեշտ է գրել պրովայդերներ, որոնք տրամադրում են մեկ օրինակ յուրաքանչյուր շղթայի համար: Պարզապես պետք է պատվիրակ փոխանցեք ThreadLocal կոնստրուկտորին, որը կվերաբերի հենց մեր օրինակի արժեքը ստանալուն: Այս դեպքում ես որոշեցի օգտագործել մեկ սկզբնական արժեք (seed)՝ նախաստորագրելով այն Environment.TickCount-ով (այսպես է աշխատում Random կոնստրուկտորն առանց պարամետրերի)։ Ավելին, ստացված տիզերի քանակը ավելանում է ամեն անգամ, երբ մենք պետք է ստանանք Random-ի նոր օրինակ առանձին շղթայի համար:

Ստորև բերված դասը լիովին ստատիկ է և պարունակում է միայն մեկ հանրային (հրապարակային) GetThreadRandom մեթոդ: Այս մեթոդը ձևավորվում է որպես մեթոդ, քան հատկություն, հիմնականում հարմարության համար. այն կդարձնի բոլոր դասերը, որոնց անհրաժեշտ է Random-ի օրինակ, կախված կլինեն Func-ից: (պատվիրակ, որը մատնանշում է մի մեթոդ, որը ոչ մի պարամետր չի վերցնում և վերադարձնում է Random տեսակի արժեք), և ոչ թե Random դասից: Եթե ​​տեսակը նախատեսված է մեկ շղթայի վրա գործարկելու համար, այն կարող է կանչել պատվիրակին՝ Random-ի մեկ օրինակ ստանալու համար, այնուհետև օգտագործել այն ամենուր; եթե տեսակը պետք է աշխատի բազմաթելային սցենարներով, այն կարող է կանչել պատվիրակին ամեն անգամ, երբ պատահական թվերի գեներատորի կարիք ունի: Ստորև բերված դասը կստեղծի Random դասի այնքան օրինակներ, որքան կան թելեր, և յուրաքանչյուր օրինակ կսկսվի տարբեր սկզբնական արժեքից: Եթե ​​մեզ անհրաժեշտ է օգտագործել պատահական թվերի մատակարարը որպես կախվածություն այլ տեսակներից, մենք կարող ենք դա անել՝ նոր TypeThatNeedsRandom(RandomProvider.GetThreadRandom): Դե, ահա ինքնին կոդը.
համակարգի օգտագործումը; օգտագործելով System.Threading; հանրային ստատիկ դաս RandomProvider ( մասնավոր ստատիկ int seed = Environment.TickCount; մասնավոր ստատիկ ThreadLocal randomWrapper = նոր ThreadLocal (() => new Random(Interlocked.Increment(ref seed))); հանրային ստատիկ Պատահական GetThreadRandom() ( վերադարձ randomWrapper.Value; ) )
Բավականին պարզ, չէ՞: Դա պայմանավորված է նրանով, որ ամբողջ ծածկագիրը ուղղված է Random-ի ճիշտ օրինակը թողարկելուն: Երբ օրինակը ստեղծվել և վերադարձվել է, այնքան էլ կարևոր չէ, թե ինչ եք անելու դրա հետ հետո. ատյանների բոլոր հետագա թողարկումները լիովին անկախ են ընթացիկից: Իհարկե, հաճախորդի կոդը վնասակար չարաշահումների բացթողում ունի. այն կարող է ստանալ Random-ի մեկ օրինակ և փոխանցել այն այլ շղթաների՝ այդ մյուս շղթաներում մեր RandomProvider-ին կանչելու փոխարեն:

Ինտերֆեյսի նախագծման խնդիրներ

Մի խնդիր դեռ մնում է. մենք օգտագործում ենք թույլ անվտանգ պատահական թվերի գեներատոր: Ինչպես նշվեց ավելի վաղ, RandomNumberGenerator-ում կա RNG-ի շատ ավելի անվտանգ տարբերակ բոլոր առումներով, որի իրականացումը գտնվում է RNGCryptoServiceProvider դասում։ Այնուամենայնիվ, նրա API-ն բավականին դժվար է օգտագործել ստանդարտ սցենարներում:

Շատ լավ կլիներ, որ շրջանակում RNG պրովայդերները առանձին «պատահականության աղբյուրներ» ունենային։ Նման դեպքում մենք կարող ենք ունենալ մեկ պարզ և հարմար API, որը կաջակցվի ինչպես անապահով, բայց արագ իրականացմամբ, այնպես էլ անվտանգ, բայց դանդաղ: Դե երազելն էլ չի խանգարի։ Հավանաբար, նմանատիպ գործառույթներ կհայտնվեն .NET Framework-ի հետագա տարբերակներում: Միգուցե Microsoft-ից ոչ մեկը կառաջարկի ադապտերի իրագործումը: (Ցավոք սրտի, ես այդպիսի մեկը չեմ լինի... ճիշտ անելը զարմանալիորեն բարդ է:) Դուք կարող եք նաև ստեղծել ձեր սեփական դասը՝ ենթադասելով Random-ը և վերացնելով Sample և NextBytes մեթոդները, բայց հստակ չէ, թե ինչպես են դրանք պետք: աշխատել, և նույնիսկ ձեր սեփական Նմուշի իրականացումը կարող է շատ ավելի բարդ լինել, քան թվում է: Գուցե հաջորդ անգամ…

Խնդրում եմ դադար տալ AdBlock-ի աշխատանքըայդ կայքում։

Երբեմն կարող է անհրաժեշտ լինել պատահական թվեր ստեղծել: Պարզ օրինակ.

Օրինակ՝ հաղթողի որոշումը վերահրապարակման մրցույթում:

53 հոգանոց ցուցակ կա։ Նրանցից պետք է հաղթող ընտրել: Եթե ​​դուք ինքներդ ընտրեք այն, ապա ձեզ կարող են մեղադրել կողմնակալության մեջ։ Այսպիսով, դուք որոշեցիք ծրագիր գրել: Այն կաշխատի հետևյալ կերպ. Դուք մուտքագրում եք մասնակիցների թիվը N , որից հետո ծրագիրը ցուցադրում է մեկ թիվ՝ հաղթողի թիվը։

Դուք արդեն գիտեք, թե ինչպես կարելի է թվեր ստանալ խաղացողից: Բայց ինչպե՞ս ստիպել համակարգչին պատահական թիվ գուշակել: Այս դասում դուք կսովորեք, թե ինչպես:

rand() ֆունկցիան:

Այս ֆունկցիան վերադարձնում է պատահական ամբողջ թիվ զրոյի և RAND_MAX-ի միջև: RAND_MAX-ը հատուկ C հաստատուն է, որը պարունակում է առավելագույն ամբողջ թիվը, որը կարող է վերադարձվել rand() ֆունկցիայի միջոցով:

rand() ֆունկցիան սահմանված է stdlib.h վերնագրի ֆայլում։ Հետևաբար, եթե ցանկանում եք օգտագործել ռանդը ձեր ծրագրում, մի մոռացեք ներառել այս վերնագրի ֆայլը: RAND_MAX հաստատունը նույնպես սահմանված է այս ֆայլում: Դուք կարող եք գտնել այս ֆայլը ձեր համակարգչում և տեսնել դրա նշանակությունը:

Եկեք տեսնենք այս հատկությունը գործողության մեջ: Եկեք գործարկենք հետևյալ կոդը.

Ցուցակ 1.

#ներառում // printf ֆունկցիան օգտագործելու համար #include // օգտագործել rand ֆունկցիան int main(void) ( /* առաջացնում է հինգ պատահական ամբողջ թվեր */ printf("%d\n", rand()); printf("%d\n", rand()); printf ("%d\n", rand()); printf("%d\n", rand()); printf("%d\n", rand());

Պետք է այսպիսի բան ստացվի.

Նկ.1 Հինգ պատահական թվեր, որոնք ստեղծվել են rand ֆունկցիայի կողմից

Բայց մենք կցանկանայինք ստանալ 1-ից մինչև 53 թվեր, ոչ թե ամեն ինչ: Ահա rand() ֆունկցիայի վրա սահմանափակումներ դնելու մի քանի հնարքներ:

Վերևից սահմանափակեք պատահական թվերը:

Ով դպրոցում սպասում էր այն պահին, երբ մաթեմատիկան օգտակար կլինի, պատրաստվիր։ Եկել է այս պահը։ Վերևից պատահական թվերը սահմանափակելու համար կարող եք օգտագործել բաժանումից մնացորդը ստանալու գործողությունը, որն ուսումնասիրել եք վերջին դասին։ Հավանաբար գիտեք, որ K թվերի բաժանման մնացորդը միշտ փոքր է K թվից: Օրինակ, 4-ի բաժանելը կարող է հանգեցնել 0, 1, 2 և 3 մնացորդների: Այսպիսով, եթե ցանկանում եք վերևից պատահական թվերը սահմանափակել մինչև K թիվը, ապա պարզապես վերցրեք բաժանման մնացորդը K-ով: Սրա նման:

Ցուցակ 2.

#ներառում #ներառում int main(void) ( /* առաջացնում է հինգ պատահական ամբողջ թիվ 100-ից փոքր */ printf("%d\n", rand()%100); printf("%d\n", rand()%100); printf ("%d\n", rand()%100); printf("%d\n", rand()%100); printf("%d\n", rand()%100); )


Նկ.2 100-ից փոքր հինգ պատահական թվեր

Սահմանափակեք ստորև նշված թվերը:

Rand ֆունկցիան վերադարձնում է պատահական թվեր տիրույթից: Բայց ի՞նչ, եթե մենք միայն ուզում ենք M-ից մեծ թվեր (օրինակ՝ 1000): Ինչպե՞ս լինել: Ամեն ինչ պարզ է. Եկեք ուղղակի ավելացնենք մեր M արժեքը, թե ինչ վերադարձրեց ռանդը: Այնուհետև եթե ֆունկցիան վերադարձնի 0, ապա վերջնական պատասխանը կլինի M, եթե 2394, ապա վերջնական պատասխանը կլինի M+2394: Այս գործողությամբ մենք բոլոր թվերն առաջ ենք տեղափոխում M միավորներով:

Սահմանեք rand ֆունկցիայի սահմանները վերևում և ներքևում:

Օրինակ, ստացեք 80-ից 100 թվեր: Կարծես թե վերը նշված երկու մեթոդները համադրելու խնդիր է: Մենք ստանում ենք նման բան.

Ցուցակ 3.

#ներառում #ներառում int main(void) ( /* առաջացնում է հինգ պատահական ամբողջ թիվ 80-ից մեծ և 100-ից փոքր */ printf("%d\n", 80 + rand()%100); printf("%d\n", 80 + rand ()%100); printf("%d\n", 80 + rand()%100); printf("%d\n", 80 + rand()%100); printf("%d\n ", 80 + rand()%100);)

Փորձեք գործարկել այս ծրագիրը: Զարմացա՞ծ:

Այո, այս ճանապարհը չի աշխատի: Եկեք գործարկենք այս ծրագիրը ձեռքով, որպեսզի տեսնենք, թե արդյոք սխալ ենք թույլ տվել: Ենթադրենք rand()-ը վերադարձրեց 143 թիվը։ 100-ի բաժանելուց հետո մնացածը 43 է։ Հետագա 80 + 43 = 123: Այսպիսով, այս մեթոդը չի աշխատում: Նմանատիպ շինարարությունը կտա 80-ից 179 թվեր:

Եկեք նայենք մեր արտահայտությանը. rand()%100-ը կարող է վերադարձնել թվեր 0-ից մինչև 99-ը ներառյալ: Նրանք. կտրվածքից։
Operation + 80-ը մեր հատվածը տեղափոխում է 80 միավոր դեպի աջ: Մենք ստանում ենք.
Ինչպես տեսնում եք, մեր խնդիրն ընկած է հատվածի աջ եզրագծում, այն աջ է տեղաշարժվում 79 միավորով։ Սա մեր սկզբնական թիվն է 80 հանած 1: Եկեք մաքրենք խառնաշփոթը և հետ տեղափոխենք աջ եզրագիծը՝ 80 + rand()%(100 - 80 + 1) : Հետո ամեն ինչ պետք է աշխատի այնպես, ինչպես պետք է:

IN ընդհանուր դեպքեթե հատվածից պետք է թվեր ստանանք, ապա պետք է օգտագործենք հետևյալ կառուցվածքը.
A + rand()%(B-A+1) .

Այս բանաձևի համաձայն մենք վերագրում ենք մեր վերջին ծրագիր:

Ցուցակ 4.

#ներառում #ներառում int main(void) ( /* առաջացնում է հինգ պատահական ամբողջ թվեր */ 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);

Աշխատանքի արդյունք.


Նկ.3 Պատահական թվեր տիրույթից

Դե, հիմա կարող եք լուծել դասի բուն խնդիրը։ Հատվածից առաջացրու թիվ: Թե՞ չես կարող։

Բայց մինչ այդ՝ մի փոքր ավելին օգտակար տեղեկատվություն. Գործարկեք վերջին ծրագիրը երեք անգամ անընդմեջ և գրեք դրա ստեղծած պատահական թվերը: Նկատե՞լ եք:

strand() ֆունկցիան:

Այո, ամեն անգամ հայտնվում են նույն նույն թվերը։ «Այսպես գեներատոր»: դու ասում ես. Եվ դուք լիովին ճիշտ չեք լինի: Իրոք, անընդհատ նույն թվերն են ստեղծվում: Բայց մենք կարող ենք ազդել դրա վրա, դրա համար մենք օգտագործում ենք srand() ֆունկցիան, որը նույնպես սահմանված է stdlib.h վերնագրի ֆայլում։ Այն սկզբնավորում է պատահական թվերի գեներատորը սերմի միջոցով:

Կազմեք և գործարկեք այս ծրագիրը մի քանի անգամ.

Ցուցակ 5.

#ներառում #ներառում int main(void) ( srand(2); /* առաջացնում է հինգ պատահական ամբողջ թվեր */ 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); )

Այժմ փոխեք srand() ֆունկցիայի արգումենտը մեկ այլ թվի (հուսով եմ, որ դեռ չեք մոռացել, թե որն է ֆունկցիայի արգումենտը) և նորից կազմեք և գործարկեք ծրագիրը։ Թվերի հաջորդականությունը պետք է փոխվի. Հենց փոխում ենք արգումենտը srand ֆունկցիայի մեջ, փոխվում է նաև հաջորդականությունը։ Շատ գործնական չէ, չէ՞: Հերթականությունը փոխելու համար դուք պետք է նորից կազմեք ծրագիրը: Եթե ​​միայն այս թիվը ավտոմատ կերպով տեղադրվի այնտեղ:

Եվ դա կարելի է անել: Օրինակ՝ օգտագործենք ժամանակի գործառույթ() , որը սահմանված է վերնագրի ֆայլի time.h . Այս ֆունկցիան, եթե NULL-ը փոխանցվում է որպես արգումենտ, վերադարձնում է 1970 թվականի հունվարի 1-ից սկսած վայրկյանների քանակը: Ահա թե ինչպես է դա արվում:

Ցուցակ 6.

#ներառում #ներառում #ներառում // օգտագործելու համար time() ֆունկցիան int main(void) ( srand(time(NULL)); /* առաջացնում է հինգ պատահական ամբողջ թվեր */ 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); )

Ի՞նչ է NULL-ը, դուք հարցնում եք: Խելամիտ հարց. Եվ ես ձեզ առայժմ կպատասխանեմ, որ սա հատուկ վերապահված խոսք է։ Կարող եմ ասել նաև, թե ինչ է նշանակում զրոյական ցուցիչ, բայց քանի որ դա ձեզ ոչ մի տեղեկություն չի տալիս, այս պահինԽորհուրդ եմ տալիս չմտածել այդ մասին։ Եվ պարզապես հիշեք որպես ինչ-որ խորամանկ հնարք: Հետագա դասերում մենք ավելի մանրամասն կանդրադառնանք այս բանին:

Շատ հաճախ ծրագրերում անհրաժեշտություն է առաջանում օգտագործել պատահական թվեր՝ զանգվածի լրացումից մինչև ծածկագրություն։ Պատահական թվերի հաջորդականություն ստանալու համար C#-ը տրամադրում է Random դասը։ Այս դասը տրամադրում է երկու կոնստրուկտոր.

  • Պատահական ()- Նախաձեռնում է Random դասի օրինակը սկզբնական արժեքով, որը կախված է ընթացիկ ժամանակից: Ինչպես գիտեք, ժամանակը կարելի է ներկայացնել ticks- 0001 թվականի հունվարի 1-ից սկսած 100-ն զարկերակները: Իսկ տիզերի ժամանակի արժեքը 64-բիթանոց ամբողջ թիվ է, որը կօգտագործվի պատահական թվերի գեներատորի օրինակը սկզբնավորելու համար:
  • Պատահական (Int32)- Նախաձեռնում է Random դասի օրինակը նշված սկզբնական արժեքով: Պատահական թվերի գեներատորի այս սկզբնավորումը կարող է հարմար լինել ծրագրի վրիպազերծման փուլում, քանի որ այս դեպքում նույն «պատահական» թվերը կստեղծվեն ամեն անգամ, երբ ծրագիրը գործարկվի:
Այս դասի հիմնական մեթոդը Next() մեթոդն է, որը թույլ է տալիս ստանալ պատահական թիվ և ունի մի շարք գերբեռնումներ.
  • Next() - վերադարձնում է պատահական ոչ բացասական Int32 ամբողջ թիվ:
  • Հաջորդ ( int32)- վերադարձնում է պատահական ոչ բացասական ամբողջ թիվ, որը փոքր է նշված արժեքից:
  • Հաջորդ ( Int32 րոպե, Int32 առավելագույն)- վերադարձնում է պատահական ամբողջ թիվ նշված տիրույթում: Այս դեպքում պայմանը min
Ինչպես նաև մեթոդներ
  • Հաջորդ բայթ ( բայթ)- բայթերի նշված զանգվածի տարրերը լրացնում է պատահական թվերով:
  • NextDouble() - վերադարձնում է պատահական լողացող կետի թիվը, տիրույթում )
    ընդմիջում ; // Համընկնում է գտնվել, տարրը չի համընկնում
    }
    եթե (j == i)
    { // համընկնում չի գտնվել
    a[i] = թիվ; // պահպանել տարրը
    i++; // անցնել հաջորդ տարրին
    }
    }
    համար (int i = 0; i< 100; i++)
    {

    եթե (i % 10 == 9)
    Console.WriteLine();
    }
    Console.ReadKey();
    }
    }
    }

    Այնուամենայնիվ, որքան մոտ է զանգվածի ավարտին, այնքան ավելի շատ սերունդներ պետք է կատարվեն՝ չկրկնվող արժեք ստանալու համար:
    Հետևյալ օրինակը ցույց է տալիս յուրաքանչյուր տարր ստանալու համար Next() մեթոդի կանչերի քանակը, ինչպես նաև պատահական թվերի ընդհանուր թիվը, որոնք ստեղծվել են 100 տարրերից բաղկացած զանգվածը չկրկնվող արժեքներով համալրելու համար:

    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

    համակարգի օգտագործումը;
    Անվանատարածք MyProgram
    {
    դասի ծրագիր
    {
    ստատիկ դատարկ Հիմնական (լարային արկեր)
    {
    Պատահական rnd = նոր Պատահական ();
    int a = նոր int; // տարրերի զանգված
    int count = նոր int; // սերունդների քանակի զանգված
    a = rnd.Next(0, 101);
    int c = 0; // սերունդների քանակի հաշվիչ
    հաշվարկ = 1; // ա ստեղծվում է միայն մեկ անգամ
    համար (int i = 1; i< 100;)
    {
    int num = rnd.Next(0, 101);
    c++; // ևս մեկ անգամ գեներացրել է տարրը
    int j;
    համար (j = 0; j< i; j++)
    {
    եթե (համար == a[j])
    ընդմիջում ;
    }
    եթե (j == i)
    {
    a[i] = թիվ; i++;
    count[i] = c; c = 0; // պահպանել սերունդների թիվը
    }
    }
    // Ցուցադրել տարրերի արժեքները
    Վահանակով .WriteLine( «Տարրերի արժեքներ»);
    համար (int i = 0; i< 100; i++)
    {
    Console .Write("(0,4) " , a[i]);
    եթե (i % 10 == 9)
    Console.WriteLine();
    }
    Console.WriteLine();
    // Ցուցադրել սերունդների թիվը
    Վահանակով .WriteLine( «Նյութերի սերունդների թիվը»);
    int sum = 0;
    համար (int i = 0; i< 100; i++)
    {
    գումար += հաշվարկ[i];
    Console .Write("(0,4) " , count[i]);
    եթե (i % 10 == 9)
    Console.WriteLine();
    }
    Վահանակով .WriteLine( «Սերունդների ընդհանուր թիվը՝ (0)», գումար);
    Console.ReadKey();
    }
    }
    }

    void Main (լարային արկեր)
    {
    Պատահական rnd = նոր Պատահական ();
    int a = նոր int;
    համար (int i = 0; i< 100; i++)
    a[i] = i;
    համար (int i = 0; i< 50; i++)
    {
    int i1 = rnd.Next (0, 100); // առաջին ինդեքս
    int i2 = rnd.Next(0, 100); // երկրորդ ինդեքս
    // տարրերի արժեքների փոխանակում i1 և i2 ինդեքսներով
    int temp = a;
    a = a;
    a = ջերմաստիճան;
    }
    Վահանակով .WriteLine( «Տարրերի արժեքներ»);
    համար (int i = 0; i< 100; i++)
    {
    Console .Write("(0,4) " , a[i]);
    եթե (i % 10 == 9)
    Console.WriteLine();
    }
    Console.ReadKey();
    }
    }
    }

    Արժեքների խառնումն ավելի արդյունավետ է, եթե արժեքների միջակայքը նույնն է (կամ մոտ է) դրանց թվին, քանի որ այս դեպքում պատահական տարրերի սերունդների թիվը զգալիորեն կրճատվում է:

    Նեղ շրջանակներում լայնորեն հայտնի պատահական թվեր հոդվածի թարգմանությունը՝ Jon Skeet. Ես կանգ առա այս հոդվածի վրա, քանի որ մի ժամանակ ես ինքս հանդիպեցի դրանում նկարագրված խնդրին:

    Թերթիր թեմաները ըստ .NETԵվ C# StackOverflow կայքում դուք կարող եք տեսնել «պատահական» բառի հիշատակման հետ կապված անթիվ հարցեր, որոնցում, փաստորեն, բարձրացվում է նույն հավերժական և «անկոտրում» հարցը՝ ինչու «չի աշխատում» System.Random պատահական թվերի գեներատորը։ և ինչպես «շտկել» այն »: Այս հոդվածը նվիրված է այս խնդրի քննարկմանը և դրա լուծման ուղիներին:

    Խնդրի ձևակերպում

    StackOverflow-ում, նորությունների խմբերում և փոստային ցուցակներում, «պատահական»-ի մասին բոլոր հարցերը հնչում են այսպես.
    Ես օգտագործում եմ Random.Next-ը մի քանի պատահական թվեր ստեղծելու համար, բայց մեթոդը վերադարձնում է նույն համարը մի քանի զանգերի դեպքում: Թիվը փոխվում է ամեն անգամ, երբ դիմումը գործարկվում է, բայց մեկ ծրագրի կատարման ընթացքում այն ​​հաստատուն է:

    Որպես օրինակ կոդ տրված է հետևյալը.
    // Վատ կոդ: Չեն օգտագործում! համար (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit()); } ... static int GenerateDigit() { Random rng = new Random(); // Предположим, что здесь много логики return rng.Next(10); }
    Այսպիսով, ինչն է սխալ այստեղ:

    Բացատրություն

    Random դասը իրական պատահական թվերի գեներատոր չէ, այն պարունակում է գեներատոր կեղծպատահական թվեր. Random դասի յուրաքանչյուր օրինակ պարունակում է որոշակի ներքին վիճակ, և երբ կանչվում է Next (կամ NextDouble, կամ NextBytes) մեթոդը, մեթոդն օգտագործում է այս վիճակը՝ վերադարձնելու համար պատահական թվեր: Դրանից հետո ներքին վիճակն այնպես է փոխվում, որ հաջորդ անգամ, երբ հաջորդ մեթոդը կանչվի, այն կվերադարձնի այլ թվացող պատահական թիվ, քան նախկինում վերադարձվածը։

    Random դասի աշխատանքի բոլոր «ներսերը»։ լիովին դետերմինիստական. Սա նշանակում է, որ եթե դուք վերցնում եք Random դասի մի քանի օրինակներ նույն սկզբնական վիճակով, որը սահմանված է կոնստրուկտոր պարամետրի միջոցով. սերմ, և յուրաքանչյուր օրինակի համար զանգահարեք որոշակի մեթոդներ նույն հերթականությամբ և նույն պարամետրերով, դուք կստանաք նույն արդյունքները:

    Այսպիսով, ինչն է սխալ վերը նշված կոդը: Վատն այն է, որ մենք օգտագործում ենք Random դասի նոր օրինակ օղակի յուրաքանչյուր կրկնության ներսում: Պատահական կոնստրուկտորը, որը ոչ մի պարամետր չի ընդունում, ընդունում է ընթացիկ ամսաթիվը և ժամը որպես սերմ (սկզբնական վիճակ): Շրջանակի կրկնությունները այնքան արագ են «պտտվում», որ ավարտվելուց հետո համակարգի ժամանակը «ժամանակ չի ունենում փոխվելու». Այսպիսով, Random-ի բոլոր օրինակները կստանան նույն արժեքը, ինչ իրենց սկզբնական վիճակը և, հետևաբար, կվերադարձնեն նույն կեղծ պատահական թիվը:

    Ինչպե՞ս ուղղել այն:

    Խնդրի լուծման բազմաթիվ տարբերակներ կան, որոնցից յուրաքանչյուրն ունի իր դրական և բացասական կողմերը: Մենք կանդրադառնանք դրանցից մի քանիսին:
    Օգտագործելով գաղտնագրված պատահական թվերի գեներատոր
    .NET-ը պարունակում է RandomNumberGenerator աբստրակտ դաս, որից պետք է ժառանգեն գաղտնագրված պատահական թվերի գեներատորների բոլոր իրականացումները (այսուհետ՝ կրիպտո RNG): .NET-ը պարունակում է նաև այս իրականացումներից մեկը՝ հանդիպել RNGCryptoServiceProvider դասին: CryptoRNG-ի գաղափարն այն է, որ նույնիսկ եթե այն դեռևս կեղծ պատահական թվերի գեներատոր է, այն ապահովում է արդյունքների բավականին ուժեղ անկանխատեսելիություն: RNGCryptoServiceProvider-ն օգտագործում է էնտրոպիայի մի քանի աղբյուրներ, որոնք արդյունավետորեն «աղմուկ» են ձեր համակարգչում, և նրա ստեղծած թվերի հաջորդականությունը շատ դժվար է կանխատեսել: Ավելին, «համակարգչային» աղմուկը կարող է օգտագործվել ոչ միայն որպես սկզբնական վիճակ, այլև հաջորդ պատահական համարների զանգերի միջև. Այսպիսով, նույնիսկ իմանալով դասի ներկա վիճակը, դա բավարար չի լինի հաշվարկելու և՛ հաջորդ թվերը, որոնք կստեղծվեն ապագայում, և՛ նրանք, որոնք ստեղծվել են ավելի վաղ: Իրականում ճշգրիտ վարքագիծը կախված է իրականացումից: Բացի այդ, Windows-ը կարող է օգտագործել մասնագիտացված սարքավորում, որը «իսկական պատահականության» աղբյուր է (օրինակ, դա կարող է լինել ռադիոակտիվ իզոտոպի քայքայման սենսոր)՝ նույնիսկ ավելի ապահով և հուսալի պատահական թվեր ստեղծելու համար:

    Եկեք համեմատենք սա նախկինում քննարկված Random դասի հետ: Ենթադրենք, դուք զանգել եք Random.Next(100) տասը անգամ և պահպանել արդյունքները: Եթե ​​ունեք բավարար հաշվողական հզորություն, կարող եք հաշվարկել նախնական վիճակը (seed), որով Random օրինակը ստեղծվել է զուտ այս արդյունքների հիման վրա, կանխատեսել Random.Next(100) կանչելու հաջորդ արդյունքները և նույնիսկ հաշվարկել նախորդ մեթոդի կանչերի արդյունքները: . Այս պահվածքը աղետալիորեն անընդունելի է, եթե դուք օգտագործում եք պատահական թվեր անվտանգության, ֆինանսական նպատակներով և այլն: Crypto RNG-ները զգալիորեն ավելի դանդաղ են աշխատում, քան Random դասը, բայց առաջացնում են թվերի հաջորդականություն, որոնցից յուրաքանչյուրն ավելի անկախ և անկանխատեսելի է մյուսների արժեքներից:

    Շատ դեպքերում դանդաղ կատարումը խոչընդոտ չէ, դա վատ API է: RandomNumberGenerator-ը նախատեսված է բայթերի հաջորդականություններ ստեղծելու համար, և վերջ: Համեմատեք սա Random դասի մեթոդների հետ, որտեղ հնարավոր է ստանալ պատահական ամբողջ թիվ, կոտորակային թիվ, ինչպես նաև բայթերի հավաքածու։ Մեկ այլ օգտակար հատկություն է նշված տիրույթում պատահական թիվ ստանալու հնարավորությունը: Համեմատեք այս հնարավորությունները պատահական բայթերի զանգվածի հետ, որն արտադրում է RandomNumberGenerator-ը: Դուք կարող եք շտկել իրավիճակը՝ ստեղծելով ձեր սեփական փաթաթան RandomNumberGenerator-ի շուրջ, որը պատահական բայթերը կվերածի «հարմար» արդյունքի, սակայն այս լուծումը մանրուք չէ:

    Այնուամենայնիվ, շատ դեպքերում Random դասի «թուլությունը» լավ է, եթե կարողանաք լուծել հոդվածի սկզբում նկարագրված խնդիրը: Եկեք տեսնենք, թե ինչ կարելի է անել այստեղ:

    Օգտագործեք Random դասի մեկ օրինակ մի քանի զանգերի ժամանակ
    Ահա, խնդրի լուծման արմատը Random-ի միայն մեկ օրինակ օգտագործելն է՝ Random.Next-ի միջոցով բազմաթիվ պատահական թվեր ստեղծելիս: Եվ դա շատ պարզ է՝ տեսեք, թե ինչպես կարող եք փոխել վերը նշված կոդը.
    // Այս կոդը ավելի լավ կլինի Random rng = new Random(); համար (int i = 0; i< 100; i++) { Console.WriteLine(GenerateDigit(rng)); } ... static int GenerateDigit(Random rng) { // Предположим, что здесь много логики return rng.Next(10); }
    Այժմ յուրաքանչյուր կրկնություն կունենա տարբեր թվեր... բայց դա դեռ ամենը չէ: Ի՞նչ կպատահի, եթե մենք երկու անգամ անընդմեջ կանչենք կոդի այս բլոկը: Ճիշտ է, մենք կստեղծենք Random-ի երկու օրինակ՝ նույն սկզբնական արժեքով և կավարտենք պատահական թվերի երկու նույնական խմբերով: Յուրաքանչյուր հավաքածուում թվերը կտարբերվեն, բայց այդ հավաքածուները միմյանց միջև հավասար կլինեն:

    Խնդիրը լուծելու երկու ճանապարհ կա. Նախ, մենք կարող ենք օգտագործել ոչ թե օրինակ, այլ ստատիկ դաշտ, որը պարունակում է Random-ի օրինակ, այնուհետև վերը նշված կոդի կտորը կստեղծի միայն մեկ օրինակ և կօգտագործի այն՝ կանչվելով այնքան, որքան ցանկանում ենք: Երկրորդ, մենք կարող ենք ընդհանրապես հեռացնել Random-ի օրինակի ստեղծումը այնտեղից՝ տեղափոխելով այն «ավելի բարձր», իդեալական՝ ծրագրի հենց «վերև», որտեղ կստեղծվի Random-ի մեկ օրինակ, որից հետո այն կտեղափոխվի: դեպի բոլոր այն վայրերը, որտեղ պատահական թվեր են անհրաժեշտ: Դա հիանալի գաղափար է և լավ արտահայտված կախվածություններով, բայց այն կաշխատի այնքան ժամանակ, քանի դեռ մենք օգտագործում ենք միայն մեկ թեմա:

    թելի անվտանգություն

    Պատահական դասը շղթայից անվտանգ չէ: Հաշվի առնելով, թե ինչպես ենք մենք սիրում ստեղծել մեկ օրինակ և օգտագործել այն ծրագրի ողջ ընթացքում դրա կատարման ընթացքում (միայնակ, բարև), թելի անվտանգության բացակայությունը դառնում է իսկական փուշ: Ի վերջո, եթե մենք օգտագործում ենք մեկ օրինակ միաժամանակ մի քանի թելերում, ապա դրա ներքին վիճակի վերակայման հնարավորություն կա, և եթե դա տեղի ունենա, ապա այդ պահից ինստանցիան անպիտան կդառնա։

    Կրկին խնդիրը լուծելու երկու ճանապարհ կա. Առաջին ուղին դեռևս ենթադրում է մեկ օրինակ, բայց այս անգամ օգտագործելով ռեսուրսի վրա մոնիտորի կողպումը: Դա անելու համար դուք պետք է ստեղծեք Random-ի շուրջ փաթաթված, որը կփաթաթի զանգը դեպի իր մեթոդները կողպման հայտարարության մեջ՝ ապահովելով բացառիկ մուտք դեպի օրինակ: Այս ճանապարհը վատ է նրանով, որ նվազեցնում է կատարումը բազմաշերտ ինտենսիվ սցենարներում:

    Մեկ այլ եղանակ, որը ես կներկայացնեմ ստորև, յուրաքանչյուր շղթայի համար մեկ օրինակ օգտագործելն է: Միակ բանը, որ մենք պետք է համոզվենք, որ մենք օգտագործում ենք տարբեր սկզբնական արժեքներ (seed) օրինակներ ստեղծելիս, և, հետևաբար, մենք չենք կարող օգտագործել լռելյայն կոնստրուկտորներ: Մնացած բոլոր առումներով այս ճանապարհը համեմատաբար պարզ է:

    Ապահով մատակարար

    Բարեբախտաբար, ThreadLocal-ի նոր ընդհանուր դասը .NET 4-ում ներկայացված .NET 4-ում շատ հեշտ է գրել պրովայդերներ, որոնք տրամադրում են մեկ օրինակ յուրաքանչյուր շղթայի համար: Պարզապես պետք է պատվիրակ փոխանցեք ThreadLocal կոնստրուկտորին, որը կվերաբերի հենց մեր օրինակի արժեքը ստանալուն: Այս դեպքում ես որոշեցի օգտագործել մեկ սկզբնական արժեք (seed)՝ նախաստորագրելով այն Environment.TickCount-ով (այսպես է աշխատում Random կոնստրուկտորն առանց պարամետրերի)։ Ավելին, ստացված տիզերի քանակը ավելանում է ամեն անգամ, երբ մենք պետք է ստանանք Random-ի նոր օրինակ առանձին շղթայի համար:

    Ստորև բերված դասը լիովին ստատիկ է և պարունակում է միայն մեկ հանրային (հրապարակային) GetThreadRandom մեթոդ: Այս մեթոդը ձևավորվում է որպես մեթոդ, քան հատկություն, հիմնականում հարմարության համար. այն կդարձնի բոլոր դասերը, որոնց անհրաժեշտ է Random-ի օրինակ, կախված կլինեն Func-ից: (պատվիրակ, որը մատնանշում է մի մեթոդ, որը ոչ մի պարամետր չի վերցնում և վերադարձնում է Random տեսակի արժեք), և ոչ թե Random դասից: Եթե ​​տեսակը նախատեսված է մեկ շղթայի վրա գործարկելու համար, այն կարող է կանչել պատվիրակին՝ Random-ի մեկ օրինակ ստանալու համար, այնուհետև օգտագործել այն ամենուր; եթե տեսակը պետք է աշխատի բազմաթելային սցենարներով, այն կարող է կանչել պատվիրակին ամեն անգամ, երբ պատահական թվերի գեներատորի կարիք ունի: Ստորև բերված դասը կստեղծի Random դասի այնքան օրինակներ, որքան կան թելեր, և յուրաքանչյուր օրինակ կսկսվի տարբեր սկզբնական արժեքից: Եթե ​​մեզ անհրաժեշտ է օգտագործել պատահական թվերի մատակարարը որպես կախվածություն այլ տեսակներից, մենք կարող ենք դա անել՝ նոր TypeThatNeedsRandom(RandomProvider.GetThreadRandom): Դե, ահա ինքնին կոդը.
    համակարգի օգտագործումը; օգտագործելով System.Threading; հանրային ստատիկ դաս RandomProvider ( մասնավոր ստատիկ int seed = Environment.TickCount; մասնավոր ստատիկ ThreadLocal randomWrapper = նոր ThreadLocal (() => new Random(Interlocked.Increment(ref seed))); հանրային ստատիկ Պատահական GetThreadRandom() ( վերադարձ randomWrapper.Value; ) )
    Բավականին պարզ, չէ՞: Դա պայմանավորված է նրանով, որ ամբողջ ծածկագիրը ուղղված է Random-ի ճիշտ օրինակը թողարկելուն: Երբ օրինակը ստեղծվել և վերադարձվել է, այնքան էլ կարևոր չէ, թե ինչ եք անելու դրա հետ հետո. ատյանների բոլոր հետագա թողարկումները լիովին անկախ են ընթացիկից: Իհարկե, հաճախորդի կոդը վնասակար չարաշահումների բացթողում ունի. այն կարող է ստանալ Random-ի մեկ օրինակ և փոխանցել այն այլ շղթաների՝ այդ մյուս շղթաներում մեր RandomProvider-ին կանչելու փոխարեն:

    Ինտերֆեյսի նախագծման խնդիրներ

    Մի խնդիր դեռ մնում է. մենք օգտագործում ենք թույլ անվտանգ պատահական թվերի գեներատոր: Ինչպես նշվեց ավելի վաղ, RandomNumberGenerator-ում կա RNG-ի շատ ավելի անվտանգ տարբերակ բոլոր առումներով, որի իրականացումը գտնվում է RNGCryptoServiceProvider դասում։ Այնուամենայնիվ, նրա API-ն բավականին դժվար է օգտագործել ստանդարտ սցենարներում:

    Շատ լավ կլիներ, որ շրջանակում RNG պրովայդերները առանձին «պատահականության աղբյուրներ» ունենային։ Նման դեպքում մենք կարող ենք ունենալ մեկ պարզ և հարմար API, որը կաջակցվի ինչպես անապահով, բայց արագ իրականացմամբ, այնպես էլ անվտանգ, բայց դանդաղ: Դե երազելն էլ չի խանգարի։ Հավանաբար, նմանատիպ գործառույթներ կհայտնվեն .NET Framework-ի հետագա տարբերակներում: Միգուցե Microsoft-ից ոչ մեկը կառաջարկի ադապտերի իրագործումը: (Ցավոք սրտի, ես այդպիսի մեկը չեմ լինի... ճիշտ անելը զարմանալիորեն բարդ է:) Դուք կարող եք նաև ստեղծել ձեր սեփական դասը՝ ենթադասելով Random-ը և վերացնելով Sample և NextBytes մեթոդները, բայց հստակ չէ, թե ինչպես են դրանք պետք: աշխատել, և նույնիսկ ձեր սեփական Նմուշի իրականացումը կարող է շատ ավելի բարդ լինել, քան թվում է: Գուցե հաջորդ անգամ…