it-swarm.com.de

gewichtetes zufälliges Element erhalten

Ich habe zum Beispiel diese Tabelle

 + ----------------- + 
 | Obst | Gewicht | 
 + ----------------- + 
 | Apple | 4 | 
 | Orange | 2 | 
 | Zitrone | 1 | 
 + ----------- ------ + 

Ich muss eine zufällige Frucht zurückgeben. Aber Apple sollte 4-mal so häufig wie Lemon und 2-mal so häufig wie orange ausgewählt werden.

Im allgemeineren Fall sollte es f(weight) mal häufig sein.

Was ist ein guter allgemeiner Algorithmus, um dieses Verhalten zu implementieren?

Oder gibt es vielleicht ein paar fertige Edelsteine ​​auf Ruby? :)

[~ # ~] ps [~ # ~]
Ich habe den aktuellen Algorithmus in Ruby https://github.com/fl00r/pickup implementiert

50
fl00r

Die konzeptionell einfachste Lösung wäre, eine Liste zu erstellen, in der jedes Element so oft vorkommt wie sein Gewicht

fruits = [Apple, Apple, Apple, Apple, orange, orange, lemon]

Verwenden Sie dann die Funktionen, die Ihnen zur Verfügung stehen, um ein zufälliges Element aus dieser Liste auszuwählen (z. B. einen Zufallsindex innerhalb des richtigen Bereichs zu generieren). Dies ist natürlich nicht sehr speichereffizient und erfordert ganzzahlige Gewichte.


Ein anderer, etwas komplizierterer Ansatz würde folgendermaßen aussehen:

  1. Berechnen Sie die kumulierten Gewichtssummen:

    intervals = [4, 6, 7]
    

    Wobei ein Index von unter 4 ein Apfel darstellt, 4 bis unter 6 ein orange und 6 bis unter 7 a Zitrone.

  2. Generieren Sie eine Zufallszahl n im Bereich von 0 Bis sum(weights).

  3. Suchen Sie das letzte Element, dessen kumulative Summe über n liegt. Die entsprechende Frucht ist Ihr Ergebnis.

Dieser Ansatz erfordert komplizierteren Code als der erste, jedoch weniger Speicher und Berechnung und unterstützt Gleitkommagewichte.

Für jeden Algorithmus kann der Einrichtungsschritt einmal für eine beliebige Anzahl von zufälligen Auswahlen durchgeführt werden.

49

Hier ist ein Algorithmus (in C #), der zufällig gewichtete Elemente aus einer beliebigen Sequenz auswählen und nur einmal durchlaufen kann:

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. 
}

Dies basiert auf der folgenden Überlegung: Wählen wir das erste Element unserer Sequenz als "aktuelles Ergebnis" aus. Behalten Sie es dann bei jeder Iteration bei oder verwerfen Sie es und wählen Sie ein neues Element als aktuell aus. Wir können die Wahrscheinlichkeit berechnen, dass ein bestimmtes Element am Ende als Produkt aller Wahrscheinlichkeiten ausgewählt wird, dass es würde nicht in nachfolgenden Schritten verworfen wird, multipliziert mit der Wahrscheinlichkeit, dass es überhaupt ausgewählt wird . Wenn Sie rechnen, werden Sie feststellen, dass sich dieses Produkt zu (Gewicht des Elements)/(Summe aller Gewichte) vereinfacht, was genau das ist, was wir brauchen!

Da diese Methode die Eingabesequenz nur einmal durchläuft, funktioniert sie auch bei übermäßig großen Sequenzen, vorausgesetzt, die Summe der Gewichte passt in ein int (oder Sie können einen größeren Typ für diesen Zähler auswählen).

30
Nevermind

Bereits vorhandene Antworten sind gut und ich werde sie ein wenig erweitern.

Wie Benjamin vorschlug, werden kumulative Summen typischerweise für diese Art von Problem verwendet:

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

Um ein Element in dieser Struktur zu finden, können Sie so etwas wie Neverminds Code verwenden. Dieses Stück C # -Code, das ich normalerweise benutze:

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

Nun zum interessanten Teil. Wie effizient ist dieser Ansatz und welche Lösung ist am effizientesten? Mein Code benötigt O (n) Speicher und läuft in O (n) Zeit. Ich denke nicht, dass es mit weniger als O (n) Raum gemacht werden kann, aber die zeitliche Komplexität kann viel geringer sein, O (log n) in der Tat . Der Trick besteht darin, die binäre Suche anstelle der regulären for-Schleife zu verwenden.

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];
}

Es gibt auch eine Geschichte über das Aktualisieren von Gewichten. Im schlimmsten Fall führt die Aktualisierung der Gewichtung für ein Element zur Aktualisierung der kumulierten Summen für alle Elemente, wodurch die Aktualisierungskomplexität auf O (n) erhöht wird. Auch das kann mit binär indizierter Baum auf O (log n) gekürzt werden.

21
Emperor Orionii

Dies ist eine einfache Python Implementierung:

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

und

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

print select(population, weights)

In genetischen Algorithmen wird dieses Auswahlverfahren Fitness proportionale Auswahl oder Roulette-Radauswahl genannt, da:

  • ein Anteil des Rades wird jeder der möglichen Auswahlen basierend auf ihrem Gewichtswert zugewiesen. Dies kann erreicht werden, indem das Gewicht einer Auswahl durch das Gesamtgewicht aller Auswahlen dividiert und dadurch auf 1 normalisiert wird.
  • dann wird eine zufällige Auswahl getroffen, ähnlich wie das Roulette-Rad gedreht wird.

Roulette wheel selection

Typische Algorithmen haben eine Komplexität von O(N) oder O (log N), aber Sie können auch O(1) (zB Roulette-Rad)) ausführen Auswahl über stochastische Akzeptanz ).

8
manlio

This Gist macht genau das, wonach Sie fragen.

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;
    }

sie können es so verwenden:

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

Der obige Code gibt höchstwahrscheinlich (% 98) 0 zurück, was der Index für 'Apple' für das angegebene Array ist.

Dieser Code testet auch die oben angegebene Methode:

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);

Es gibt eine Ausgabe wie folgt:

Start...
Head count:52
Tails count:48
0
Ramazan Polat