Generowanie Ważonej Liczby Losowej

Staram się wymyślić (dobry) sposób, aby wybrać losową liczbę z zakresu możliwych liczb, gdzie każda liczba w zakresie ma wagę. Mówiąc prościej: biorąc pod uwagę zakres liczb (0,1,2) Wybierz liczbę, w której 0 mA 80% prawdopodobieństwa wyboru, 1 mA 10% szansy, a 2 ma 10% szansy.

Minęło około 8 lat od moich zajęć z statystyki na studiach, więc możecie sobie wyobrazić właściwą formułę na to mi w tej chwili umyka.

Oto "tanie i brudne" metoda, którą wymyśliłem. Rozwiązanie to wykorzystuje ColdFusion. Twój może używać dowolnego języka. Jestem programistą, myślę, że poradzę sobie z portowaniem. Ostatecznie moje rozwiązanie musi być w Groovy - napisałem to w ColdFusion, ponieważ łatwo jest szybko napisać / przetestować w CF.

public function weightedRandom( Struct options ) {

    var tempArr = [];

    for( var o in arguments.options )
    {
        var weight = arguments.options[ o ] * 10;
        for ( var i = 1; i<= weight; i++ )
        {
            arrayAppend( tempArr, o );
        }
    }
    return tempArr[ randRange( 1, arrayLen( tempArr ) ) ];
}

// test it
opts = { 0=.8, 1=.1, 2=.1  };

for( x = 1; x<=10; x++ )
{
    writeDump( weightedRandom( opts ) );    
}

Szukam lepszych rozwiązań, proszę sugerować ulepszenia lub alternatywy.

Author: Daniel A. White, 2011-12-08

10 answers

Odrzucanie próbkowania (takie jak w Twoim rozwiązaniu) jest pierwszą rzeczą, która przychodzi na myśl, kiedy budujesz tabelę wyszukiwania z elementami wypełnionymi przez ich rozkład wagowy, a następnie wybierasz losową lokalizację w tabeli i zwracasz ją. Jako wybór implementacji, chciałbym zrobić funkcję wyższego rzędu, która pobiera spec i zwraca funkcję, która zwraca wartości na podstawie rozkładu w spec, w ten sposób można uniknąć konieczności budowania tabeli dla każdego wywołania. Minusy są takie, że algorytmiczna wydajność budowania tabeli jest liniowa według liczby elementów i potencjalnie może być dużo wykorzystania pamięci dla dużych specyfikacji (lub tych z prętami o bardzo małych lub precyzyjnych wagach, np. {0:0.99999, 1:0.00001}). Plusem jest to, że zbieranie wartości ma stały czas, który może być pożądany, jeśli wydajność jest krytyczna. W JavaScript:

function weightedRand(spec) {
  var i, j, table=[];
  for (i in spec) {
    // The constant 10 below should be computed based on the
    // weights in the spec for a correct and optimal table size.
    // E.g. the spec {0:0.999, 1:0.001} will break this impl.
    for (j=0; j<spec[i]*10; j++) {
      table.push(i);
    }
  }
  return function() {
    return table[Math.floor(Math.random() * table.length)];
  }
}
var rand012 = weightedRand({0:0.8, 1:0.1, 2:0.1});
rand012(); // random in distribution...

Inną strategią jest wybranie losowej liczby w [0,1) i iteracja specyfikacji wagi sumującej wagi, jeśli liczba losowa jest mniejsza niż suma, zwróć powiązaną wartość. Oczywiście zakłada to, że wagi sumują się do jednego. Rozwiązanie to nie ma kosztów wstępnych, ale ma średnią wydajność algorytmiczną liniową przez liczbę wpisów w specyfikacji. Na przykład w JavaScript:

function weightedRand2(spec) {
  var i, sum=0, r=Math.random();
  for (i in spec) {
    sum += spec[i];
    if (r <= sum) return i;
  }
}
weightedRand2({0:0.8, 1:0.1, 2:0.1}); // random in distribution...
 54
Author: maerics,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-08 18:45:29

Wygeneruj losową liczbę R z zakresu od 0 do 1.

If R in [0, 0.1) -> 1

If R in [0.1, 0.2) -> 2

If R in [0.2, 1] -> 3

Jeśli nie możesz bezpośrednio uzyskać liczby z zakresu od 0 do 1, Wygeneruj liczbę w zakresie, który da tyle precyzji, ile chcesz. Na przykład, jeśli masz wagi dla

(1, 83.7%) i (2, 16,3%), rzucać liczbę od 1 do 1000. 1-837 to 1. 838-1000 to 2.

 18
Author: Thomas Eding,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-01 05:08:12

Jest to mniej więcej generyczna wersja tego, co napisał @trinithis w Javie: zrobiłem to z ints, a nie pływakami, aby uniknąć niechlujnych błędów zaokrąglania.

static class Weighting {

    int value;
    int weighting;

    public Weighting(int v, int w) {
        this.value = v;
        this.weighting = w;
    }

}

public static int weightedRandom(List<Weighting> weightingOptions) {

    //determine sum of all weightings
    int total = 0;
    for (Weighting w : weightingOptions) {
        total += w.weighting;
    }

    //select a random value between 0 and our total
    int random = new Random().nextInt(total);

    //loop thru our weightings until we arrive at the correct one
    int current = 0;
    for (Weighting w : weightingOptions) {
        current += w.weighting;
        if (random < current)
            return w.value;
    }

    //shouldn't happen.
    return -1;
}

public static void main(String[] args) {

    List<Weighting> weightings = new ArrayList<Weighting>();
    weightings.add(new Weighting(0, 8));
    weightings.add(new Weighting(1, 1));
    weightings.add(new Weighting(2, 1));

    for (int i = 0; i < 100; i++) {
        System.out.println(weightedRandom(weightings));
    }
}
 10
Author: Greg Case,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-08 18:11:21

Oto 3 rozwiązania w javascript, ponieważ nie jestem pewien, w którym języku chcesz go w. W zależności od potrzeb jeden z dwóch pierwszych może działać, ale trzeci jest prawdopodobnie najłatwiejszy do wdrożenia przy dużych zestawach liczb.

function randomSimple(){
  return [0,0,0,0,0,0,0,0,1,2][Math.floor(Math.random()*10)];
}

function randomCase(){
  var n=Math.floor(Math.random()*100)
  switch(n){
    case n<80:
      return 0;
    case n<90:
      return 1;
    case n<100:
      return 2;
  }
}

function randomLoop(weight,num){
  var n=Math.floor(Math.random()*100),amt=0;
  for(var i=0;i<weight.length;i++){
    //amt+=weight[i]; *alternative method
    //if(n<amt){
    if(n<weight[i]){
      return num[i];
    }
  }
}

weight=[80,90,100];
//weight=[80,10,10]; *alternative method
num=[0,1,2]
 8
Author: qw3n,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-08 18:00:43

A może

Int [ ] liczby = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 2 } ;

Następnie możesz wybrać losowo spośród liczb, a 0 będzie miało 80% szans, 1 10% i 2 10%

 5
Author: emory,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-08 17:44:37

Używam następujących

function weightedRandom(min, max) {
  return Math.round(max / (Math.random() * max + min));
}

To jest mój "ważony" losowy, gdzie używam funkcji odwrotnej " x " (gdzie x jest losowy między min i max), aby wygenerować wynik ważony, gdzie minimum jest najcięższym elementem, a maksimum najlżejszym (najmniejsze szanse na uzyskanie wyniku)

Więc zasadniczo, użycie weightedRandom(1, 5) oznacza, że szanse na uzyskanie 1 są wyższe niż 2, które są wyższe niż 3, które są wyższe niż 4, które są wyższe niż 5.

Może nie być przydatne w przypadku użycia, ale prawdopodobnie przydatne dla osób googlujących to samo pytanie.

Po próbie 100 iteracji dał mi:

==================
| Result | Times |
==================
|      1 |    55 |
|      2 |    28 |
|      3 |     8 |
|      4 |     7 |
|      5 |     2 |
==================
 5
Author: Tom Roggero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-16 16:16:27

Ten jest w Mathematica, ale łatwo go skopiować do innego języka, używam go w moich grach i radzi sobie z wagami dziesiętnymi:

weights = {0.5,1,2}; // The weights
weights = N@weights/Total@weights // Normalize weights so that the list's sum is always 1.
min = 0; // First min value should be 0
max = weights[[1]]; // First max value should be the first element of the newly created weights list. Note that in Mathematica the first element has index of 1, not 0.
random = RandomReal[]; // Generate a random float from 0 to 1;
For[i = 1, i <= Length@weights, i++,
    If[random >= min && random < max,
        Print["Chosen index number: " <> ToString@i]
    ];
    min += weights[[i]];
    If[i == Length@weights,
        max = 1,
        max += weights[[i + 1]]
    ]
]

(teraz mówię o liście pierwszego elementu indeks jest równy 0) ideą tego jest to, że mając znormalizowaną listę wagi istnieje szansa, że wagi[n] zwróci indeks n , więc odległości między min i max Na kroku npowinny być wagi[n] {6]}. Całkowita odległość od minimum min (które określamy jako 0) , a maksimum max jest sumą listy wag.

Dobrą rzeczą jest to, że nie dodajesz do żadnej tablicy lub zagnieżdżasz pętli, co znacznie wydłuża czas wykonania.

Oto kod w C# bez potrzeby normalizacji listy wag i usuwania jakiegoś kodu:

int WeightedRandom(List<float> weights) {
    float total = 0f;
    foreach (float weight in weights) {
        total += weight;
    }

    float max = weights [0],
    random = Random.Range(0f, total);

    for (int index = 0; index < weights.Count; index++) {
        if (random < max) {
            return index;
        } else if (index == weights.Count - 1) {
            return weights.Count-1;
        }
        max += weights[index+1];
    }
    return -1;
}
 1
Author: Garmekain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-11 14:57:03

Oto dane wejściowe i współczynniki : 0 (80%), 1(10%) , 2 (10%)

Pozwala narysować je tak, aby było łatwe do wizualizacji.

                0                       1        2
-------------------------------------________+++++++++

Dodajmy całkowitą wagę i nazwijmy ją TR dla całkowitej proporcji. więc w tym przypadku 100. pozwala losowo uzyskać liczbę od (0-TR) lub (0 do 100 w tym przypadku). 100 to Twoja waga. Nazwij to RN dla liczby losowej.

Więc teraz mamy TR jako całkowitą wagę i RN jako liczbę losową między 0 A TR.

Więc wyobraźmy sobie, że wybraliśmy losowy # z Od 0 do 100. Powiedzmy 21. czyli 21%.

MUSIMY PRZEKONWERTOWAĆ / DOPASOWAĆ TO DO NASZYCH LICZB WEJŚCIOWYCH, ALE JAK ?

Lets loop over each weight (80, 10, 10) and keep the sum of the wag we already visited. w momencie, gdy suma wag, nad którymi zapętlamy jest większa niż liczba losowa RN( 21 w tym przypadku), zatrzymujemy pętlę i zwracamy pozycję tego elementu.

double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 21) //(80 > 21) so break on first pass
break;
}
//position will be 0 so we return array[0]--> 0

Powiedzmy, że liczba losowa (od 0 do 100) to 83. Zróbmy to jeszcze raz:

double sum = 0;
int position = -1;
for(double weight : weight){
position ++;
sum = sum + weight;
if(sum > 83) //(90 > 83) so break
break;
}

//we did two passes in the loop so position is 1 so we return array[1]---> 1
 1
Author: j2emanue,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-23 06:32:29

Sugeruję ciągłe sprawdzanie prawdopodobieństwa i reszty losowej liczby.

Ta funkcja ustawia najpierw wartość zwracaną na ostatni możliwy indeks i jest powtarzana, dopóki reszta wartości losowej nie będzie mniejsza niż rzeczywiste prawdopodobieństwo.

Prawdopodobieństwo musi być sumowane do jednego.

function getRandomIndexByProbability(probabilities) {
    var r = Math.random(),
        index = probabilities.length - 1;

    probabilities.some(function (probability, i) {
        if (r < probability) {
            index = i;
            return true;
        }
        r -= probability;
    });
    return index;
}

var i,
    probabilities = [0.8, 0.1, 0.1],
    count = probabilities.map(function () { return 0; });

for (i = 0; i < 1e6; i++) {
    count[getRandomIndexByProbability(probabilities)]++;
}

console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }
 0
Author: Nina Scholz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-06 11:08:28

Mam slotmachine i użyłem poniższego kodu do generowania losowych liczb. W probabilitiesSlotMachine klucze są wyjściem w maszynie slotmachine, a wartości reprezentują wagę.

const probabilitiesSlotMachine         = [{0 : 1000}, {1 : 100}, {2 : 50}, {3 : 30}, {4 : 20}, {5 : 10}, {6 : 5}, {7 : 4}, {8 : 2}, {9 : 1}]
var allSlotMachineResults              = []

probabilitiesSlotMachine.forEach(function(obj, index){
    for (var key in obj){
        for (var loop = 0; loop < obj[key]; loop ++){
            allSlotMachineResults.push(key)
        }
    }
});

Teraz, aby wygenerować losowe wyjście, używam tego kodu:

const random = allSlotMachineResults[Math.floor(Math.random() * allSlotMachineResults.length)]
 0
Author: J. Doe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-10 22:43:48