रैंड () छोटी रेंज के लिए फिर से वही नंबर देता है


9

मैं एक ऐसे खेल को बनाने की कोशिश कर रहा हूं जिसमें मेरे पास 20x20 का ग्रिड है और मैं एक खिलाड़ी (पी), एक लक्ष्य (टी) और तीन दुश्मन (एक्स) प्रदर्शित करता हूं। इन सभी में एक X और Y एक समन्वय है जिसे उपयोग करके सौंपा गया है rand()। समस्या यह है कि अगर मैं खेल में अधिक अंक प्राप्त करने की कोशिश करता हूं (ऊर्जा आदि के लिए रिफिल) तो वे एक या अधिक अन्य बिंदुओं के साथ ओवरलैप करते हैं क्योंकि सीमा छोटी (1 से 20 समावेशी) होती है।

ये मेरी चर हैं और मैं उन्हें कैसे बताए रहा हूँ महत्व देता है: ( COORDएक है structसिर्फ एक एक्स और एक वाई के साथ)

const int gridSize = 20;
COORD player;
COORD target;
COORD enemy1;
COORD enemy2;
COORD enemy3;

//generate player
srand ( time ( NULL ) );
spawn(&player);
//generate target
spawn(&target);
//generate enemies
spawn(&enemy1);
spawn(&enemy2);
spawn(&enemy3);

void spawn(COORD *point)
{
    //allot X and Y coordinate to a point
    point->X = randNum();
    point->Y = randNum();
}

int randNum()
{
    //generate a random number between 1 and gridSize
    return (rand() % gridSize) + 1;
}

मैं खेल में और चीजें जोड़ना चाहता हूं लेकिन जब मैं ऐसा करता हूं तो ओवरलैप की संभावना बढ़ जाती है। क्या इसको ठीक करने का कोई तरीका है?


8
रैंड () एक बुरा RNG है
शाफ़्ट सनकी

3
rand()एक दयालु RNG है, और वैसे भी इस तरह की एक छोटी सी सीमा के साथ, आपको बस टकराव की उम्मीद नहीं है , वे लगभग गारंटी हैं।
डेडुप्लिकेटर

1
हालांकि यह सच है कि rand()एक घटिया RNG है, यह शायद एकल खिलाड़ी खेल के लिए उपयुक्त है, और RNG गुणवत्ता यहाँ समस्या नहीं है।
रोबोट

13
rand()लगता है कि गुणवत्ता के बारे में बोलना यहाँ अप्रासंगिक है। इसमें कोई भी क्रिप्टोग्राफी शामिल नहीं है, और कोई भी आरएनजी बहुत कम मानचित्र में संभवतः टक्कर देगा।
टॉम कॉर्निज़ जूल

2
आप जो देख रहे हैं उसे बर्थडे प्रॉब्लम के रूप में जाना जाता है। यदि आपके रैंडम नंबर PRNG की प्राकृतिक रेंज से छोटी सीमा में परिवर्तित हो रहे हैं तो एक ही नंबर के दो इंस्टेंसेस प्राप्त करने की संभावना आपके विचार से बहुत अधिक है। कुछ समय पहले मैंने इस विषय पर यहां
ConcernedOfTunbridgeWells

जवाबों:


40

जबकि उपयोगकर्ताओं को जो शिकायत करते हैं rand()और बेहतर आरएनजी की सिफारिश करते हैं वे यादृच्छिक संख्याओं की गुणवत्ता के बारे में सही हैं, वे बड़ी तस्वीर को भी याद कर रहे हैं। रैंडम नंबरों की धाराओं में डुप्लिकेट से बचा नहीं जा सकता है, वे जीवन का एक तथ्य हैं। यह जन्मदिन की समस्या का सबक है

२० * २० = ४०० संभावित स्पॉन पदों की ग्रिड पर, केवल २४ संस्थाओं को स्पैनिंग करते समय भी एक डुप्लिकेट स्पॉन पॉइंट की उम्मीद (५०% संभावना) की जाती है। 50 संस्थाओं (अभी भी पूरे ग्रिड का केवल 12.5%) के साथ, एक डुप्लिकेट की संभावना 95% से अधिक है। आपको टकराव से निपटना होगा।

कभी-कभी आप सभी नमूनों को एक बार में आकर्षित कर सकते हैं, फिर आप गारंटीकृत-विशिष्ट वस्तुओं को आकर्षित करने के लिए एक फेरबदल एल्गोरिथ्म का उपयोग कर सकते हैं n। आपको बस सभी संभावनाओं की सूची तैयार करनी होगी। यदि स्टोर करने के लिए संभावनाओं की पूरी सूची बहुत बड़ी है, तो आप एक समय में एक स्पॉन स्थिति उत्पन्न कर सकते हैं जैसा कि आप अभी करते हैं (बस एक बेहतर आरएनजी के साथ) और टक्कर होने पर बस फिर से उत्पन्न करते हैं। हालांकि कुछ टकराव होने की संभावना है, भले ही अधिकांश ग्रिड के आबादी में होने के बावजूद एक पंक्ति में कई टकराव की संभावना नहीं है।


मैंने टक्कर के मामले में रिस्पना के बारे में सोचा था, लेकिन अगर मेरे पास अधिक आइटम हैं, जैसा कि मेरा इरादा है, तो टकराव की तलाश जटिल हो जाएगी। मुझे गेम से एक बिंदु को जोड़ने या हटाने के मामले में चेक को संपादित करना होगा। मैं काफी अनुभवहीन हूं इसलिए अगर इसमें कोई बदलाव है, तो मैं इसे नहीं देख सकता।
रबीज रियाज

7
यदि आपके पास 20x20 निरंतर (वास्तविक) XY विमान के विपरीत 20x20 चेकबोर्ड है, तो आपके पास टक्करों की जांच के लिए 400-सेल लुकअप टेबल क्या है। यह TRIVIAL है।
बजे जॉन आर। स्ट्रॉहेम जूल

@RabeezRiaz यदि आपके पास एक बड़ा मानचित्र है, तो आपके पास कुछ ग्रिड-आधारित डेटा संरचना होगी (एक ग्रिड जिसमें कोशिकाओं के कुछ क्षेत्र शामिल हैं, और उस सेल के अंदर प्रत्येक आइटम एक सूची में संग्रहीत है)। यदि आपका नक्शा अभी बड़ा है, तो आप रेक्ट-ट्री को लागू करेंगे।
रॉन्ग

2
@RabeezRiaz: यदि लुकअप बहुत जटिल है, तो उसके पहले सुझाव का उपयोग करें: सभी 400 संभावित शुरुआती स्थानों की सूची बनाएं, उन्हें फेरबदल करें ताकि वे एक यादृच्छिक क्रम में हों (एल्गोरिथ्म देखने के लिए), और तब जब आप की आवश्यकता हो तो सामने से स्थानों का उपयोग करना शुरू करें। सामान उत्पन्न करने के लिए (आपने पहले से कितने का उपयोग किया है, इस पर नज़र रखें)। कोई टकराव नहीं।
रेमकोगर्लिच

2
@RabeezRiaz को पूरी सूची में फेरबदल करने की आवश्यकता नहीं है, यदि आपको केवल बहुत कम संख्या में यादृच्छिक मूल्यों की आवश्यकता है, तो जिस हिस्से की आपको जरूरत है, उसे फेरबदल करें (1..400 की सूची से यादृच्छिक मान लें, इसे हटा दें, और तब तक दोहराएं आपके पास पर्याप्त तत्व हैं)। वास्तव में यह है कि कैसे एक फेरबदल एल्गोरिथ्म वैसे भी काम करता है।
डोरस

3

यदि आप हमेशा किसी ऐसे स्थान पर नई इकाई को चलाने से बचना चाहते हैं जो पहले से ही किसी और चीज को आवंटित किया गया है, तो आप अपनी प्रक्रिया को थोड़ा बदल सकते हैं। यह अनूठे स्थानों की गारंटी देगा, लेकिन इसके लिए कुछ अधिक ओवरहेड की आवश्यकता होती है। यहाँ कदम हैं:

  1. मानचित्र पर सभी संभावित स्थानों के संदर्भों का एक संग्रह सेट करें (20x20 मानचित्र के लिए, यह 400 स्थान होगा)
  2. 400 के इस संग्रह से यादृच्छिक पर एक स्थान चुनें (रैंड) इसके लिए ठीक काम करेगा
  3. संभावित स्थानों के संग्रह से इस संभावना को हटा दें (इसलिए अब इसमें 399 संभावनाएं हैं)
  4. तब तक दोहराएं जब तक कि सभी संस्थाओं के पास एक निर्दिष्ट स्थान न हो

जब तक आप उस सेट से स्थान को हटा रहे हैं, जिसे आप चुन रहे हैं, तब तक एक ही स्थान प्राप्त करने वाली दूसरी इकाई का कोई मौका नहीं होना चाहिए (जब तक कि आप एक से अधिक थ्रेड से स्थानों को नहीं उठा रहे हों)।

इस के लिए एक वास्तविक दुनिया एनालॉग कार्ड के एक डेक से एक कार्ड ड्राइंग होगा। वर्तमान में, आप डेक को फेरबदल कर रहे हैं, एक कार्ड खींच रहे हैं और इसे नीचे चिह्नित कर रहे हैं, खींचे गए कार्ड को वापस डेक में डाल रहे हैं, फिर से फेरबदल कर रहे हैं और फिर से ड्राइंग कर रहे हैं। उपर्युक्त दृष्टिकोण कार्ड को वापस डेक में डालता है।


1

rand() % nआदर्श से कम होने से संबंधित

rand() % nगैर-समान वितरण के लिए डूइंग है। आपको कुछ निश्चित मानों की अनुपातहीन संख्या मिल जाएगी क्योंकि मानों की संख्या 20 से अधिक नहीं है

अगला, rand()आम तौर पर एक रैखिक बधाई जनरेटर है ( कई अन्य हैं , बस यह सबसे अधिक संभावना है जिसे लागू किया गया है - और आदर्श मापदंडों से कम के साथ (मापदंडों का चयन करने के कई तरीके हैं)। इसके साथ सबसे बड़ी समस्या यह है कि अक्सर इसमें कम बिट्स (जिन्हें आप एक % 20प्रकार की अभिव्यक्ति के साथ प्राप्त करते हैं) यादृच्छिक नहीं हैं। मैं एक याद rand()साल पहले से जहां सबसे कम बिट से बारी-बारी से 1करने के लिए 0की गई प्रत्येक कॉल के साथ rand()- यह बहुत यादृच्छिक नहीं था।

से रैंड (3) आदमी पेज:

लिनक्स सी लाइब्रेरी में रैंड () और सरंड () के संस्करण समान हैं
यादृच्छिक संख्या जनरेटर यादृच्छिक के रूप में () और srandom (), इसलिए निचले क्रम
बिट्स उच्च-क्रम बिट्स के रूप में यादृच्छिक होना चाहिए। हालाँकि, पुराने पर
रैंड () कार्यान्वयन, और विभिन्न पर वर्तमान कार्यान्वयन पर
सिस्टम, निचले क्रम के बिट्स उच्चतर की तुलना में बहुत कम यादृच्छिक होते हैं-
आदेश बिट्स। इस फ़ंक्शन का उपयोग उन अनुप्रयोगों में न करें जो होने का इरादा रखते हैं
पोर्टेबल जब अच्छा यादृच्छिकता की जरूरत है।

यह अब इतिहास में फिर से शामिल किया जा सकता है, लेकिन यह बहुत संभव है कि आप अभी भी एक खराब रैंड () कार्यान्वयन को ढेर में कहीं छिपा रहे हैं। किस मामले में, यह अभी भी काफी लागू है।

करने की बात यह है कि वास्तव में एक अच्छे रैंडम नंबर लाइब्रेरी का उपयोग करें (जो अच्छे रैंडम नंबर देता है) और फिर अपनी इच्छित सीमा के भीतर रैंडम नंबर मांगें।

कोड की एक अच्छी यादृच्छिक संख्या का उदाहरण (लिंक किए गए वीडियो में 13:00 बजे से)

#include <iostream>
#include <random>
int main() {
    std::mt19937 mt(1729); // yes, this is a fixed seed
    std::uniform_int_distribution<int> dist(0, 99);
    for (int i = 0; i < 10000; i++) {
        std::cout << dist(mt) << " ";
    }
    std::cout << std::endl;
}

इसकी तुलना करें:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL));
    for (int i = 0; i < 10000; i++) {
        printf("%d ", rand() % 100);
    }
    printf("\n");
}

इन दोनों कार्यक्रमों को चलाएं और तुलना करें कि उस आउटपुट में कितनी संख्याएँ आती हैं (या ऊपर नहीं आती हैं)।

संबंधित वीडियो: रैंड () हानिकारक माना जाता है

रैंड के कुछ ऐतिहासिक पहलू () नेथेक में बग पैदा करते हैं जिन्हें किसी को अपने स्वयं के कार्यान्वयन में देखना और विचार करना चाहिए:

  • नेटहैक आरएनजी समस्या

    रैंड () नेटहॉक की यादृच्छिक संख्या पीढ़ी के लिए एक बहुत ही मौलिक कार्य है। जिस तरह से नेटहैक इसका इस्तेमाल करता है वह छोटी गाड़ी है या यह तर्क दिया जा सकता है कि lrand48 () भद्दे छद्म यादृच्छिक संख्या पैदा करता है। (हालांकि, lrand48 () एक परिभाषित PRNG विधि का उपयोग करते हुए एक पुस्तकालय समारोह है और इसका उपयोग करने वाले किसी भी कार्यक्रम को उस पद्धति की कमजोरियों को ध्यान में रखना चाहिए।)

    बग यह है कि नेटहॉक भरोसा करता है (कभी-कभी विशेष रूप से जैसा कि आरएन (2) में होता है) lrand48 () से परिणामों के निचले बिट्स पर होता है। इस वजह से पूरे खेल में आरएनजी खराब काम करती है। उपयोगकर्ता क्रियाओं को और अधिक यादृच्छिकता, अर्थात चरित्र निर्माण और पहले स्तर के निर्माण में प्रस्तुत करने से पहले यह विशेष रूप से ध्यान देने योग्य है।

हालांकि उपरोक्त 2003 से था, फिर भी इसे ध्यान में रखा जाना चाहिए क्योंकि ऐसा नहीं हो सकता है कि आपके इच्छित खेल को चलाने वाले सभी सिस्टम एक अच्छे रैंड () फ़ंक्शन के साथ लिनक्स सिस्टम तक होंगे।

तुम सिर्फ खुद के लिए यह कर रहे हैं, तो आप परीक्षण कर सकते हैं कितना अच्छा अपने यादृच्छिक संख्या जनरेटर के द्वारा होता है कुछ कोड लिखने और साथ उत्पादन के परीक्षण के ईएनटी


यादृच्छिक संख्याओं के गुणों पर

'यादृच्छिक' की अन्य व्याख्याएं हैं जो बिल्कुल यादृच्छिक नहीं हैं। डेटा की एक यादृच्छिक धारा में, समान संख्या को दो बार प्राप्त करना काफी संभव है। यदि आप एक सिक्का (यादृच्छिक) फ्लिप करते हैं, तो एक पंक्ति में दो सिर प्राप्त करना काफी संभव है। या एक पासे को दो बार फेंकें और एक ही संख्या में दो बार मिलें। या रूलेट व्हील को स्पिन करना और वहां दो बार एक ही नंबर प्राप्त करना।

संख्याओं का वितरण

जब कोई गीत सूची खेल रहा होता है, तो लोग 'यादृच्छिक' की अपेक्षा करते हैं कि एक ही गीत या कलाकार को दूसरी बार बजाया नहीं जाएगा। एक पंक्ति में दो बार प्लेलिस्ट खेलने के बाद 'यादृच्छिक नहीं' (हालांकि यह यादृच्छिक है) माना जाता है । चार गानों की एक प्ले लिस्ट के लिए यह धारणा कुल आठ बार निभाई गई:

1 3 2 4 1 2 4 3

से अधिक 'यादृच्छिक' है:

1 3 3 2 1 4 4 2

गाने के 'फेरबदल' के लिए इस पर और अधिक: गाने को कैसे फेरबदल करें?

बार-बार मान पर

यदि आप मानों को दोहराना नहीं चाहते हैं, तो एक अलग दृष्टिकोण है जिसे माना जाना चाहिए। सभी संभावित मान उत्पन्न करें, और उन्हें फेरबदल करें।

यदि आप कॉल कर रहे हैं rand()(या किसी अन्य यादृच्छिक संख्या जनरेटर), तो आप इसे प्रतिस्थापन के साथ बुला रहे हैं। आप हमेशा एक ही नंबर दो बार प्राप्त कर सकते हैं। एक विकल्प यह है कि जब तक आप अपनी आवश्यकताओं को पूरा न कर लें, तब तक मूल्यों को बार-बार टॉस करें। मैं इंगित करता हूं कि यह एक गैर-नियतात्मक रनटाइम है और यह संभव है कि आप खुद को ऐसी स्थिति में पा सकते हैं जहां एक अनंत लूप है जब तक आप एक अधिक जटिल बैक ट्रेसिंग करना शुरू नहीं करते हैं।

सूची और चुनें

एक अन्य विकल्प सभी संभावित वैध राज्यों की सूची तैयार करना है और फिर उस सूची से एक यादृच्छिक तत्व का चयन करना है। कमरे में सभी खाली स्पॉट (जो कुछ नियमों को पूरा करते हैं) ढूंढें और फिर उस सूची से एक यादृच्छिक चुनें। और फिर इसे फिर से करें और तब तक करें जब तक आप काम न करें।

मिश्रण

अन्य दृष्टिकोण यह है कि यह ताश के पत्तों की तरह है। कमरे में सभी खाली स्थानों के साथ बाहर शुरू करें और फिर खाली स्थान के लिए पूछते हुए प्रत्येक नियम / प्रक्रिया को खाली स्थानों से निपटते हुए उन्हें असाइन करना शुरू करें। आप तब करते हैं जब आप कार्ड से बाहर निकलते हैं या चीजें उनके लिए पूछना बंद कर देती हैं।


3
Next, rand() is typically a linear congruential generatorयह अब कई प्लेटफार्मों पर सच नहीं है। लाइनक्स के रैंड (3) मैन पेज से: "लिनक्स सी लाइब्रेरी में रैंड () और सरैंड () के संस्करण यादृच्छिक (3) और सरनेम (3) के रूप में एक ही यादृच्छिक संख्या जनरेटर का उपयोग करते हैं, इसलिए निचले क्रम के बिट्स उच्च-क्रम बिट्स के रूप में यादृच्छिक होना चाहिए। " इसके अलावा, जैसा कि @delnan बताते हैं, PRNG की गुणवत्ता यहां वास्तविक समस्या नहीं है।
चार्ल्स ई। ग्रांट

4
मैं इसे नीचा दिखा रहा हूं क्योंकि यह वास्तविक समस्या को हल नहीं करता है।
user253751

@immibis फिर न तो वास्तविक समस्या का "हल" करता है और न ही उसे नकारा जाना चाहिए। मुझे लगता है कि सवाल "मेरे कोड को ठीक नहीं" है, यह "मुझे डुप्लीकेट यादृच्छिक संख्या क्यों मिल रही है?" दूसरे प्रश्न के लिए, मेरा मानना ​​है कि प्रश्न का उत्तर दिया गया है।
नील

4
यहां तक ​​कि RAND_MAX32767 के सबसे छोटे मूल्य के साथ अंतर 1638 संभव है कि दूसरों के लिए कुछ संख्या बनाम 1639 प्राप्त करने के संभावित तरीके। ओपी में बहुत अधिक व्यावहारिक अंतर होने की संभावना नहीं है।
मार्टिन स्मिथ

@ नील "मेरा कोड ठीक करें" सवाल नहीं है।
ऑर्बिट

0

इस समस्या का सबसे सरल समाधान पिछले उत्तरों में उद्धृत किया गया है: यह आपकी 400 कोशिकाओं में से हर एक के साथ यादृच्छिक मूल्यों की एक सूची बनाना है, और फिर इस यादृच्छिक सूची को क्रमबद्ध करना है। आपकी कोशिकाओं की सूची को यादृच्छिक सूची के रूप में क्रमबद्ध किया जाएगा, और इस तरह से फेरबदल किया जाएगा।

इस पद्धति में बेतरतीब ढंग से चयनित कोशिकाओं के अतिव्यापीकरण से बचने का लाभ है।

नुकसान यह है कि आपको अपने प्रत्येक सेल के लिए एक अलग सूची पर एक यादृच्छिक मूल्य की गणना करनी होगी । खेल शुरू होने के दौरान आप ऐसा नहीं करेंगे।

यहाँ एक उदाहरण है कि आप इसे कैसे कर सकते हैं:

#include <algorithm>
#include <iostream>
#include <vector>

#define NUMBER_OF_SPAWNS 20
#define WIDTH 20
#define HEIGHT 20

typedef struct _COORD
{
  int x;
  int y;
  _COORD() : x(0), y(0) {}
  _COORD(int xp, int yp) : x(xp), y(yp) {}
} COORD;

typedef struct _spawnCOORD
{
  float rndValue;
  COORD*coord;
  _spawnCOORD() : rndValue(0.) {}
} spawnCOORD;

struct byRndValue {
  bool operator()(spawnCOORD const &a, spawnCOORD const &b) {
    return a.rndValue < b.rndValue;
  }
};

int main(int argc, char** argv)
{
  COORD map[WIDTH][HEIGHT];
  std::vector<spawnCOORD>       rndSpawns(WIDTH * HEIGHT);

  for (int x = 0; x < WIDTH; ++x)
    for (int y = 0; y < HEIGHT; ++y)
      {
        map[x][y].x = x;
        map[x][y].y = y;
        rndSpawns[x + y * WIDTH].coord = &(map[x][y]);
        rndSpawns[x + y * WIDTH].rndValue = rand();
      }

  std::sort(rndSpawns.begin(), rndSpawns.end(), byRndValue());

  for (int i = 0; i < NUMBER_OF_SPAWNS; ++i)
    std::cout << "Case selected for spawn : " << rndSpawns[i].coord->x << "x"
              << rndSpawns[i].coord->y << " (rnd=" << rndSpawns[i].rndValue << ")\n";
  return 0;
}

परिणाम:

root@debian6:/home/eh/testa# ./exe 
Case selected for spawn : 11x15 (rnd=6.93951e+06)
Case selected for spawn : 14x1 (rnd=7.68493e+06)
Case selected for spawn : 8x12 (rnd=8.93699e+06)
Case selected for spawn : 18x13 (rnd=1.16148e+07)
Case selected for spawn : 1x0 (rnd=3.50052e+07)
Case selected for spawn : 2x17 (rnd=4.29992e+07)
Case selected for spawn : 9x14 (rnd=7.60658e+07)
Case selected for spawn : 3x11 (rnd=8.43539e+07)
Case selected for spawn : 12x7 (rnd=8.77554e+07)
Case selected for spawn : 19x0 (rnd=1.05576e+08)
Case selected for spawn : 19x14 (rnd=1.10613e+08)
Case selected for spawn : 8x2 (rnd=1.11538e+08)
Case selected for spawn : 7x2 (rnd=1.12806e+08)
Case selected for spawn : 19x15 (rnd=1.14724e+08)
Case selected for spawn : 8x9 (rnd=1.16088e+08)
Case selected for spawn : 2x19 (rnd=1.35497e+08)
Case selected for spawn : 2x16 (rnd=1.37807e+08)
Case selected for spawn : 2x8 (rnd=1.49798e+08)
Case selected for spawn : 7x16 (rnd=1.50123e+08)
Case selected for spawn : 8x11 (rnd=1.55325e+08)

अधिक या कम यादृच्छिक सेल प्राप्त करने के लिए बस NUMBER_OF_SPAWNS को बदलें, इससे कार्य के लिए आवश्यक गणना समय नहीं बदलेगा।


"और फिर, उन सभी को छाँटने के लिए" - मेरा मानना ​​है कि आपका मतलब है "फेरबदल"

मैंने अपनी व्याख्या थोड़ी पूरी कर ली है। यह अब स्पष्ट होना चाहिए।
क्वांटरेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.