Generieren Sie eine gewichtete Zufallszahl

Lesezeit: 7 Minuten

Generieren Sie eine gewichtete Zufallszahl
Tod Sharp

Ich versuche, einen (guten) Weg zu finden, um eine Zufallszahl aus einer Reihe möglicher Zahlen auszuwählen, bei der jeder Zahl in der Reihe ein Gewicht gegeben wird. Um es einfach auszudrücken: Wählen Sie in Anbetracht des Zahlenbereichs (0,1,2) eine Zahl, bei der 0 eine Wahrscheinlichkeit von 80 % hat, ausgewählt zu werden, 1 eine Wahrscheinlichkeit von 10 % und 2 eine Wahrscheinlichkeit von 10 % hat.

Es ist ungefähr 8 Jahre her seit meinem College-Statistikkurs, also können Sie sich vorstellen, dass mir die richtige Formel dafür im Moment entgeht.

Hier ist die “billige und schmutzige” Methode, die ich mir ausgedacht habe. Diese Lösung verwendet ColdFusion. Ihre können die Sprache verwenden, die Sie möchten. Ich bin ein Programmierer, ich denke, ich kann damit umgehen, es zu portieren. Letztendlich muss meine Lösung in Groovy sein – ich habe diese in ColdFusion geschrieben, weil es einfach ist, schnell in CF zu schreiben/testen.

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

Ich suche nach besseren Lösungen, bitte schlagen Sie Verbesserungen oder Alternativen vor.

  • Ähnliche stackoverflow.com/questions/20586620/…

    – SMUsamaShah

    20. Dezember 2016 um 18:42 Uhr


Generieren Sie eine gewichtete Zufallszahl
Maerik

Ablehnungsprobenahme (wie in Ihrer Lösung) ist das erste, was mir in den Sinn kommt, wobei Sie eine Nachschlagetabelle mit Elementen erstellen, die durch ihre Gewichtsverteilung gefüllt sind, dann eine zufällige Position in der Tabelle auswählen und zurückgeben. Als Implementierungsoption würde ich eine Funktion höherer Ordnung erstellen, die eine Spezifikation übernimmt und eine Funktion zurückgibt, die Werte basierend auf der Verteilung in der Spezifikation zurückgibt. Auf diese Weise vermeiden Sie, dass Sie die Tabelle für jeden Aufruf erstellen müssen. Die Nachteile sind, dass die algorithmische Leistung beim Erstellen der Tabelle linear zur Anzahl der Elemente ist und dass möglicherweise viel Speicher für große Spezifikationen verwendet wird (oder solche mit Mitgliedern mit sehr kleinen oder präzisen Gewichtungen, z. B. {0:0,99999, 1 :0.00001}). Der Vorteil ist, dass die Auswahl eines Werts eine konstante Zeit hat, was wünschenswert sein kann, wenn die Leistung kritisch ist. In 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...

Eine andere Strategie besteht darin, eine zufällige Zahl auszuwählen [0,1) and iterate over the weight specification summing the weights, if the random number is less than the sum then return the associated value. Of course, this assumes that the weights sum to one. This solution has no up-front costs but has average algorithmic performance linear by the number of entries in the spec. For example, in 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});  // zufällig in Verteilung...

  • Beachten Sie, dass Sie ein Array speichern können, das die kumulativen Summen angibt, dh tun Sie es einmal und verwenden Sie dann a log n binäre Suche jedes Mal, wenn Sie eine Zahl generieren. Das macht aber nur bei großen n Sinn.

    – Dan-Mann

    7. Mai 2016 um 14:03 Uhr

  • Wenn ich die Funktion mit diesen Parametern arr = {0:0.1, 1:0.7, 2:0.9} 10000 Mal ausführe, erhalte ich diese Ausgabe: 0: 983, 1: 7011 und 2: 2006, was alles falsch ist, weil 2 hat mehr Wahrscheinlichkeit als 1, während Outout auf etwas anderes hindeutet.

    – Rüzgar

    6. Mai 2017 um 10:47 Uhr

  • @maerics Hey, nur eine kurze Überprüfung mit Ihnen, muss die Summe des Gewichts genau 1 sein? Ich habe dies ausprobiert weightedRand({0:0.350, 1:0.200, 2:0.010, 3:0.150 , 4:0.010, 5:0.200, 6:0.150 }); aber mir ist aufgefallen, dass Nummer 4 oft eine sehr große Zahl ergibt

    - Schwarze Mamba

    2. Dezember 2017 um 9:26 Uhr

  • @hyperfkcb ja, die Summe der Gewichte muss eins sein und für diese Gewichte müssen Sie den konstanten Wert 1000 anstelle von 10 verwenden.

    – Maerik

    2. Dezember 2017 um 15:30 Uhr


  • @maerics Danke für die Klarstellung! Aber darf ich wissen, was Sie mit dem konstanten Wert 1000 statt 10 meinen?

    - Schwarze Mamba

    6. Dezember 2017 um 7:21 Uhr

1646243709 388 Generieren Sie eine gewichtete Zufallszahl
Thomas Eding

Generieren Sie eine Zufallszahl R zwischen 0 und 1.

Wenn R drin ist [0, 0.1) -> 1

If R in [0.1, 0.2) -> 2

If R in [0.2, 1] -> 3

Wenn Sie eine Zahl zwischen 0 und 1 nicht direkt erhalten können, generieren Sie eine Zahl in einem Bereich, der so viel Genauigkeit erzeugt, wie Sie möchten. Zum Beispiel, wenn Sie die Gewichte für haben

(1, 83,7 %) und (2, 16,3 %) würfeln eine Zahl von 1 bis 1000. 1-837 ist eine 1. 838-1000 ist eine 2.

  • Das ist im Wesentlichen das, was ich schreiben wollte, aber mit Code.

    – Sammy Larbi

    8. Dezember 2011 um 17:47 Uhr

  • Ein Freund von mir hat sich diese Variante dieses Ansatzes ausgedacht: return Math.random() < 0.8 ? 0: (Math.zufällig () < 0,9? 1: 2);

    – Todd Sharp

    8. Dezember 2011 um 19:13 Uhr

  • Ich würde das nicht empfehlen, es sei denn, Sie haben es mit bedingten Wahrscheinlichkeiten zu tun, die das am besten modelliert.

    – Thomas Eding

    8. Dezember 2011 um 19:40 Uhr


  • @ToddSharp Ich weiß, es ist uralt, aber ... Sie möchten eigentlich dieselbe Zufallszahl verwenden, oder Sie erhalten eine Verzerrung: r = Math.random (); Rückkehr (r < 0,8) ? 0 : (r<.9) ? 1 : 2. In Ihrem Code würde '2' nur zurückgegeben, wenn r1>=.8 UND r2>=.9, was 10% von 20% oder 2% der Fälle sind.

    – jimm101

    6. Juli 2016 um 20:37 Uhr

Ich verwende folgendes

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

Dies ist mein "gewichteter" Zufall, bei dem ich eine Umkehrfunktion von "x" verwende (wobei x ein Zufall zwischen min und max ist), um ein gewichtetes Ergebnis zu generieren, bei dem das Minimum das schwerste Element und das Maximum ist das leichteste (geringste Chancen, das Ergebnis zu erhalten)

Also im Grunde mit weightedRandom(1, 5) bedeutet, dass die Chancen, eine 1 zu bekommen, höher sind als eine 2, die höher sind als eine 3, die höher sind als eine 4, die höher sind als eine 5.

Könnte für Ihren Anwendungsfall nicht nützlich sein, aber wahrscheinlich nützlich für Leute, die dieselbe Frage googeln.

Nach 100 Iterationen hat es mir Folgendes gegeben:

==================
| Result | Times |
==================
|      1 |    55 |
|      2 |    28 |
|      3 |     8 |
|      4 |     7 |
|      5 |     2 |
==================

  • Welche Anwendungsfälle gibt es dafür? Ich habe es versucht weightedRandom(50, 100) aber immer noch 1s und so erhalten, habe ich offensichtlich den Punkt verpasst.

    – Solo

    15. April 2019 um 22:41 Uhr

  • @Solo ein paar Dinge: (1) Dieser Ansatz ist sehr spezifisch, da er den niedrigsten Zahlen ein großes Gewicht (Priorität) beimisst f(x)=1/x ... (2) Da es zufällig verwendet wird, gibt es keine Garantie, dass es mindestens einmal jede Zahl verwendet ... und (3) zu guter Letzt sollten Sie verwenden 49 + weightedRandom(1, 51) wenn Sie Zahlen zwischen 50 und 100 erhalten möchten

    – Tom Roggero

    18. April 2019 um 20:16 Uhr

  • Duh, 49 + weightedRandom(1, 51) ist so offensichtliche Lösung. Danke.

    – Solo

    18. April 2019 um 20:19 Uhr

  • Das ist eine Top-Lösung!

    – Emmanuel NK

    19. Januar 2020 um 7:01 Uhr

  • Die perfekte Lösung, um einige Testdaten in Grafiken etwas überzeugender aussehen zu lassen. Vielen Dank für diesen cleveren kleinen Schnipsel.

    – Gegenwesen

    13. Februar 2020 um 2:54 Uhr

Hier sind 3 Lösungen in Javascript, da ich nicht sicher bin, in welcher Sprache Sie es haben möchten. Abhängig von Ihren Anforderungen könnte eine der ersten beiden funktionieren, aber die dritte ist wahrscheinlich am einfachsten mit großen Mengen von Zahlen zu implementieren.

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]

Dies ist mehr oder weniger eine generische Version dessen, was @trinithis in Java geschrieben hat: Ich habe es mit Ints statt Floats gemacht, um unordentliche Rundungsfehler zu vermeiden.

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

1646243710 864 Generieren Sie eine gewichtete Zufallszahl
WirbelP23

8 Jahre zu spät, aber hier ist meine Lösung in 4 Zeilen.

  1. Bereiten Sie eine Reihe von vor Wahrscheinlichkeit Massenfunktion so dass

pmf[array_index] = P(X=Array_Index):

var pmf = [0.8, 0.1, 0.1]
  1. Bereiten Sie ein Array für das entsprechende vor Verteilungsfunktion so dass

cdf[array_index] = F(X=Array_Index):

var cdf = pmf.map((sum => value => sum += value)(0))
// [0.8, 0.9, 1]

3a) Erzeuge eine Zufallszahl.

3b) Holen Sie sich ein Array von Elementen, die größer oder gleich dieser Zahl sind.

3c) Gib seine Länge zurück.

var r = Math.random()
cdf.filter(el => r >= el).length

1646243711 505 Generieren Sie eine gewichtete Zufallszahl
Gedächtnis

Wie wäre es mit

int [ ] Zahlen = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2};

dann können Sie zufällig aus Zahlen auswählen und 0 hat eine Chance von 80 %, 1 10 % und 2 10 %

  • Dies funktioniert, aber es ist nicht erforderlich, ein Array zuzuweisen. Was ist, wenn Sie es mit sehr genauen Gewichten wie 4,68342 % zu tun haben? Sie müssen ein Array mit einer Größe von mindestens 10000000 zuweisen.

    – Thomas Eding

    8. Dezember 2011 um 18:15 Uhr


914640cookie-checkGenerieren Sie eine gewichtete Zufallszahl

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy