भारहीन यादृच्छिक वस्तु प्राप्त करें


51

उदाहरण के लिए, मेरे पास यह तालिका है

+ ----------------- +
| फल | वजन |
+ ----------------- +
| सेब | 4 |
| नारंगी | 2 |
| नींबू | 1 |
+ ----------------- +

मुझे एक यादृच्छिक फल वापस करने की आवश्यकता है। लेकिन सेब को नींबू से 4 गुना और नारंगी से 2 गुना अधिक बार लेना चाहिए ।

अधिक सामान्य मामले में यह f(weight)बार-बार होना चाहिए ।

इस व्यवहार को लागू करने के लिए एक अच्छा सामान्य एल्गोरिथ्म क्या है?

या शायद रूबी पर कुछ तैयार रत्न हैं? :)

PS
मैंने रूबी https://github.com/fl00r/pickup में करंट एल्गोरिदम लागू किया है


11
यही कारण है कि डियाब्लो में यादृच्छिक लूट प्राप्त करने के लिए एक ही सूत्र होना चाहिए :-)
जलयेन

1
@ जैलेन: वास्तव में, मेरे जवाब में नीचे दिए गए अंतराल के समाधान का विचार मुझे वर्ल्ड ऑफ़ विक्टर में युद्ध की तालिकाओं के बारे में याद है। :
बेंजामिन क्लोस्टर



मैंने कई सरल भारित यादृच्छिक एल्गोरिदम लागू किए हैं । यदि तुम्हारे सवाल हों तो मुझे बताओ।
२०:२

जवाबों:


50

वैचारिक रूप से सबसे सरल समाधान एक सूची बनाना होगा जहां प्रत्येक तत्व अपने वजन के रूप में कई बार होता है, इसलिए

fruits = [apple, apple, apple, apple, orange, orange, lemon]

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


एक और, थोड़ा और अधिक जटिल दृष्टिकोण इस तरह दिखेगा:

  1. वजन के संचयी योगों की गणना करें:

    intervals = [4, 6, 7]

    जहां 4 से नीचे का सूचकांक एक सेब का प्रतिनिधित्व करता है , 4 से नीचे 6 एक नारंगी और 6 से नीचे 7 एक नींबू है

  2. एक यादृच्छिक संख्या उत्पन्न nकी सीमा में 0करने के लिए sum(weights)

  3. अंतिम आइटम खोजें जिसका संचयी योग ऊपर है n। संबंधित फल आपका परिणाम है।

इस दृष्टिकोण के लिए पहले की तुलना में अधिक जटिल कोड की आवश्यकता होती है, लेकिन कम मेमोरी और गणना और फ्लोटिंग-पॉइंट वेट का समर्थन करता है।

या तो एल्गोरिथ्म के लिए, सेटअप-चरण एक बार यादृच्छिक चयनों की मनमानी संख्या के लिए किया जा सकता है।


2
अंतराल समाधान अच्छा लगता है
जलयान

1
यह मेरा पहला विचार था :)। लेकिन क्या होगा अगर मुझे 100 फल और वजन वाली मेज 10k के आसपास मिल सकती है? यह बहुत बड़ी सरणी होगी और यह उतना कुशल नहीं होगा जितना मैं चाहता हूं। यह पहले उपाय के बारे में है। दूसरा समाधान अच्छा लगता है
fl00r

1
मैंने इस एल्गोरिथ्म को रूबी github.com/fl00r/pickup पर
fl00r

1
उपनाम विधि इसे संभालने का तरीका है जिसे मैं ईमानदारी से उन पदों की संख्या पर चकित करता हूं जो एक ही कोड को बार-बार दोहराते हैं, जबकि सभी अन्य तरीके से नजरअंदाज करते हैं । भगवान के लिए आप लगातार समय प्रदर्शन मिलता है!
ओपा

30

यहाँ एक एल्गोरिथ्म है (C # में) जो किसी भी क्रम से यादृच्छिक भारित तत्व का चयन कर सकता है, केवल एक बार इसके माध्यम से पुनरावृत्ति कर रहा है:

public static T Random<T>(this IEnumerable<T> enumerable, Func<T, int> weightFunc)
{
    int totalWeight = 0; // this stores sum of weights of all elements before current
    T selected = default(T); // currently selected element
    foreach (var data in enumerable)
    {
        int weight = weightFunc(data); // weight of current element
        int r = Random.Next(totalWeight + weight); // random value
        if (r >= totalWeight) // probability of this is weight/(totalWeight+weight)
            selected = data; // it is the probability of discarding last selected element and selecting current one instead
        totalWeight += weight; // increase weight sum
    }

    return selected; // when iterations end, selected is some element of sequence. 
}

यह निम्नलिखित तर्क पर आधारित है: हमारे अनुक्रम के पहले तत्व को "वर्तमान परिणाम" के रूप में चुनें; फिर, प्रत्येक पुनरावृत्ति पर, इसे या तो रखें या त्यागें और वर्तमान के रूप में नया तत्व चुनें। हम किसी भी दिए गए तत्व की प्रायिकता को गणना कर सकते हैं जिसे सभी संभावनाओं के उत्पाद के रूप में अंत में चुना जाएगा जिसे बाद के चरणों में खारिज नहीं किया जाएगा, यह संभावना है कि इसे पहली जगह में चुना जाएगा। यदि आप गणित करते हैं, तो आप देखेंगे कि यह उत्पाद (तत्व का वजन) / (सभी भार का योग) को सरल करता है, जो कि वास्तव में हमारी आवश्यकता है!

चूंकि यह विधि केवल एक बार इनपुट अनुक्रम पर पुनरावृत्त होती है, इसलिए यह अश्लील रूप से बड़े अनुक्रमों के साथ भी काम करता है, बशर्ते कि वजन का योग एक में फिट बैठता है int(या आप इस काउंटर के लिए कुछ बड़े प्रकार चुन सकते हैं)


2
मैं इसे बेहतर मानने से पहले बेंचमार्क करूंगा क्योंकि यह एक बार पुनरावृति करता है। कई यादृच्छिक मूल्यों को उत्पन्न करना बिल्कुल भी तेज नहीं है।
जीन-बर्नार्ड पेलरिन

1
@ जीन-बर्नार्ड पेलरिन मैंने किया, और यह वास्तव में बड़ी सूचियों पर तेज है। जब तक आप क्रिप्टोग्राफिक रूप से मजबूत यादृच्छिक जनरेटर का उपयोग नहीं करते हैं (-8
नेवरमाइंड

स्वीकृत उत्तर imo होना चाहिए। मुझे यह "अंतराल" और "दोहराया प्रविष्टि" दृष्टिकोण से बेहतर लगता है।
विविन पालीथ

2
मैं सिर्फ यह कहना चाहता हूं कि मैं इस विधि का उपयोग करने के लिए पिछले कुछ वर्षों में 3 या 4 बार इस थ्रेड पर वापस आया हूं। यह विधि बार-बार उन उत्तरों को प्रदान करने में सफल रही है जिनकी मुझे अपने उद्देश्यों के लिए शीघ्रता से आवश्यकता है। मैं चाहता हूं कि मैं इस उत्तर को हर बार इस्तेमाल करने के लिए वापस ला सकूं।
जिम यारब्रॉन

1
अच्छा समाधान अगर आपको वास्तव में केवल एक बार चुनना है। अन्यथा, पहले उत्तर में एक बार समाधान के लिए पूर्व-कार्य करना कहीं अधिक कुशल है।
डेडुप्लिकेटर

22

पहले से मौजूद उत्तर अच्छे हैं और मैं उन पर थोड़ा विस्तार करूंगा।

जैसा कि बेंजामिन ने सुझाव दिया कि संचयी रकम आमतौर पर इस तरह की समस्या में उपयोग की जाती है:

+------------------------+
| fruit  | weight | csum |
+------------------------+
| apple  |   4    |   4  |
| orange |   2    |   6  |
| lemon  |   1    |   7  |
+------------------------+

इस संरचना में एक आइटम खोजने के लिए, आप नेवरमाइंड के कोड की तरह कुछ का उपयोग कर सकते हैं। C # कोड का यह टुकड़ा जो मैं आमतौर पर उपयोग करता हूं:

double r = Random.Next() * totalSum;
for(int i = 0; i < fruit.Count; i++)
{
    if (csum[i] > r)
        return fruit[i];
}

अब दिलचस्प हिस्से में। यह दृष्टिकोण कितना कुशल है और सबसे कुशल समाधान क्या है? मेरे कोड ऑफ़ ओ (n) मेमोरी की आवश्यकता होती है और O (n) समय में चलती है । मुझे नहीं लगता कि यह O (n) स्थान से कम के साथ किया जा सकता है, लेकिन समय जटिलता बहुत कम हो सकती है, वास्तव में O (log n) । लूप के लिए नियमित बजाय बाइनरी खोज का उपयोग करने के लिए चाल है।

double r = Random.Next() * totalSum;
int lowGuess = 0;
int highGuess = fruit.Count - 1;

while (highGuess >= lowGuess)
{
    int guess = (lowGuess + highGuess) / 2;
    if ( csum[guess] < r)
        lowGuess = guess + 1;
    else if ( csum[guess] - weight[guess] > r)
        highGuess = guess - 1;
    else
        return fruit[guess];
}

वजन अपडेट करने के बारे में भी एक कहानी है। सबसे खराब स्थिति में एक तत्व के लिए वजन को अद्यतन करने से ओ (एन) के लिए अद्यतन जटिलता को बढ़ाने वाले सभी तत्वों के लिए संचयी रकम का अद्यतन होता है । वह भी बाइनरी अनुक्रमित पेड़ का उपयोग करके ओ (लॉग एन) के नीचे काटा जा सकता है ।


द्विआधारी खोज के बारे में अच्छी बात है
fl00r

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

1
आप मेरे कोड के "अधिक पठनीय संस्करण" होने का दावा करते हैं, वास्तव में ऐसा नहीं है। आपके कोड को अग्रिम में कुल वजन, और संचयी रकम जानने की जरूरत है; मेरा नहीं है
नहीं

@ बेंजामिन क्लॉस्टर मेरा कोड केवल एक बार प्रति तत्व वजन फ़ंक्शन को कॉल करता है - आप इससे बेहतर नहीं कर सकते। आप यादृच्छिक संख्याओं के बारे में सही हैं, यद्यपि।
कोई बात नहीं

@ नेवरमाइंड: आप इसे केवल पिक-फंक्शन के लिए एक बार कॉल करते हैं, इसलिए यदि उपयोगकर्ता इसे दो बार कॉल करता है, तो वजन फ़ंक्शन को प्रत्येक तत्व के लिए फिर से कहा जाता है। बेशक आप इसे कैश कर सकते हैं, लेकिन तब आप अंतरिक्ष जटिलता के लिए ओ (1) नहीं हैं।
बेंजामिन क्लोस्टर

8

यह एक साधारण पायथन कार्यान्वयन है:

from random import random

def select(container, weights):
    total_weight = float(sum(weights))
    rel_weight = [w / total_weight for w in weights]

    # Probability for each element
    probs = [sum(rel_weight[:i + 1]) for i in range(len(rel_weight))]

    slot = random()
    for (i, element) in enumerate(container):
        if slot <= probs[i]:
            break

    return element

तथा

population = ['apple','orange','lemon']
weights = [4, 2, 1]

print select(population, weights)

आनुवंशिक एल्गोरिदम में इस चयन प्रक्रिया को फिटनेस आनुपातिक चयन या रूले व्हील चयन कहा जाता है :

  • पहिया का अनुपात उनके वजन मूल्य के आधार पर संभावित चयनों में से प्रत्येक को सौंपा गया है। यह सभी चयनों के कुल वजन द्वारा एक चयन के वजन को विभाजित करके प्राप्त किया जा सकता है, जिससे उन्हें 1 तक सामान्य किया जा सकता है।
  • फिर एक यादृच्छिक चयन कैसे रूलेट पहिया घुमाया जाता है के समान बनाया जाता है।

रूले पहिया चयन

विशिष्ट एल्गोरिदम में ओ (एन) या ओ (लॉग एन) जटिलता होती है, लेकिन आप ओ (1) (जैसे रूले-व्हील का चयन स्टोकेस्टिक स्वीकृति के माध्यम से ) भी कर सकते हैं ।


क्या आप जानते हैं कि इस छवि का मूल स्रोत क्या है? मैं इसे एक कागज के लिए उपयोग करना चाहता हूं, लेकिन इसे सुनिश्चित करने की आवश्यकता है।
मैल्कम मैकलेड

@MalcolmMacLeod क्षमा करें, इसका उपयोग बहुत सारे GA कागज / साइटों में किया जाता है लेकिन मुझे नहीं पता कि लेखक कौन है।
manlio

0

यह जिन्न ठीक वही कर रहा है जो आप पूछ रहे हैं।

public static Random random = new Random(DateTime.Now.Millisecond);
public int chooseWithChance(params int[] args)
    {
        /*
         * This method takes number of chances and randomly chooses
         * one of them considering their chance to be choosen.    
         * e.g. 
         *   chooseWithChance(0,99) will most probably (%99) return 1
         *   chooseWithChance(99,1) will most probably (%99) return 0
         *   chooseWithChance(0,100) will always return 1.
         *   chooseWithChance(100,0) will always return 0.
         *   chooseWithChance(67,0) will always return 0.
         */
        int argCount = args.Length;
        int sumOfChances = 0;

        for (int i = 0; i < argCount; i++) {
            sumOfChances += args[i];
        }

        double randomDouble = random.NextDouble() * sumOfChances;

        while (sumOfChances > randomDouble)
        {
            sumOfChances -= args[argCount -1];
            argCount--;
        }

        return argCount-1;
    }

आप इसे इस तरह उपयोग कर सकते हैं:

string[] fruits = new string[] { "apple", "orange", "lemon" };
int choosenOne = chooseWithChance(98,1,1);
Console.WriteLine(fruits[choosenOne]);

उपरोक्त कोड संभवतः (% 98) रिटर्न 0 होगा जो दिए गए एरे के लिए 'ऐप्पल' के लिए इंडेक्स है।

इसके अलावा, यह कोड ऊपर दी गई विधि का परीक्षण करता है:

Console.WriteLine("Start...");
int flipCount = 100;
int headCount = 0;
int tailsCount = 0;

for (int i=0; i< flipCount; i++) {
    if (chooseWithChance(50,50) == 0)
        headCount++;
    else
        tailsCount++;
}

Console.WriteLine("Head count:"+ headCount);
Console.WriteLine("Tails count:"+ tailsCount);

यह एक आउटपुट कुछ इस तरह देता है:

Start...
Head count:52
Tails count:48

2
प्रोग्रामर वैचारिक प्रश्नों के बारे में हैं और चीजों की व्याख्या करने के लिए उत्तर अपेक्षित हैं। स्पष्टीकरण के बजाय कोड डंप फेंकना आईडीई से व्हाइटबोर्ड पर कोड कॉपी करने जैसा है: यह परिचित लग सकता है और यहां तक ​​कि कभी-कभी समझ में आता है, लेकिन यह अजीब लगता है ... बस अजीब है। व्हाइटबोर्ड में कंपाइलर नहीं है
gnat

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