2015-05-18 11 views
5

Schauen Sie sich diese Frage:Generieren von Zufallszahlen mit einer bestimmten Verteilung

Swift probability of random number being selected?

Die Top-Antwort deutet auf eine switch-Anweisung zu verwenden, die die Arbeit erledigt. Wenn ich jedoch eine sehr große Anzahl von Fällen zu berücksichtigen habe, sieht der Code sehr unelegant aus; Ich habe eine riesige switch-Anweisung mit sehr ähnlichem Code in jedem Fall immer wieder wiederholt.

Gibt es einen schöneren, saubereren Weg, eine Zufallszahl mit einer bestimmten Wahrscheinlichkeit auszuwählen, wenn Sie eine große Anzahl von Wahrscheinlichkeiten in Betracht ziehen müssen? (like ~ 30)

+0

Wenn Sie ähnliche Fälle haben, können Sie alle ähnlichen Fälle in einem einzigen Fall schreiben, indem Sie einen Komma-Operator angeben. – Amit89

+0

@ Amit89 Sie sind ähnlich, aber nicht identisch. Alle Fälle geben nur einen Wert zurück, geben aber unterschiedliche Werte zurück. – asdf

+1

Die switch-Anweisung sieht elegant zu mir ... –

Antwort

15

Dies ist eine Swift Umsetzung stark von den verschiedenen Antworten auf Generate random numbers with a given (numerical) distribution beeinflusste:

func randomNumber(#probabilities: [Double]) -> Int { 

    // Sum of all probabilities (so that we don't have to require that the sum is 1.0): 
    let sum = reduce(probabilities, 0, +) 
    // Random number in the range 0.0 <= rnd < sum : 
    let rnd = sum * Double(arc4random_uniform(UInt32.max))/Double(UInt32.max) 
    // Find the first interval of accumulated probabilities into which `rnd` falls: 
    var accum = 0.0 
    for (i, p) in enumerate(probabilities) { 
     accum += p 
     if rnd < accum { 
      return i 
     } 
    } 
    // This point might be reached due to floating point inaccuracies: 
    return (probabilities.count - 1) 
} 

Beispiele:

let x = randomNumber(probabilities: [0.2, 0.3, 0.5]) 

0 zurück mit der Wahrscheinlichkeit von 0,2, 1 mit einer Wahrscheinlichkeit von 0,3, und 2 mit Wahrscheinlichkeit 0,5.

let x = randomNumber(probabilities: [1.0, 2.0]) 

Rückgabe 0 mit Wahrscheinlichkeit 1/3 und 1 mit Wahrscheinlichkeit 2/3.


Update für Swift 2/Xcode 7:

func randomNumber(probabilities probabilities: [Double]) -> Int { 

    // Sum of all probabilities (so that we don't have to require that the sum is 1.0): 
    let sum = probabilities.reduce(0, combine: +) 
    // Random number in the range 0.0 <= rnd < sum : 
    let rnd = sum * Double(arc4random_uniform(UInt32.max))/Double(UInt32.max) 
    // Find the first interval of accumulated probabilities into which `rnd` falls: 
    var accum = 0.0 
    for (i, p) in probabilities.enumerate() { 
     accum += p 
     if rnd < accum { 
      return i 
     } 
    } 
    // This point might be reached due to floating point inaccuracies: 
    return (probabilities.count - 1) 
} 

Update für Swift 3/Xcode 8:

func randomNumber(probabilities: [Double]) -> Int { 

    // Sum of all probabilities (so that we don't have to require that the sum is 1.0): 
    let sum = probabilities.reduce(0, +) 
    // Random number in the range 0.0 <= rnd < sum : 
    let rnd = sum * Double(arc4random_uniform(UInt32.max))/Double(UInt32.max) 
    // Find the first interval of accumulated probabilities into which `rnd` falls: 
    var accum = 0.0 
    for (i, p) in probabilities.enumerated() { 
     accum += p 
     if rnd < accum { 
      return i 
     } 
    } 
    // This point might be reached due to floating point inaccuracies: 
    return (probabilities.count - 1) 
} 
+0

Vielen Dank. Ich bin nicht sicher, was Sie mit "Gleitkomma-Ungenauigkeiten" meinen - können Sie das bitte weiter ausführen? – asdf

+0

@asdf: Da 'rnd' kleiner ist als' sum', sollte die Bedingung 'if rnd für einige' i' gelten. Aber binäre Fließkommazahlen können nicht alle Werte genau darstellen, daher könnte die Schleife enden. In diesem Fall wird der letzte Index zurückgegeben. –

+0

@fqdn: Danke für die Korrektur, das sollte besser sein! - Ihre Aussage * "das Original' arc4random_uniform (UInt32.max - 1) 'könnte -1 zurückgeben" * ist nicht korrekt, es gibt eine Zahl zwischen 0 und UInt32.max - 2 zurück :) –

3

Gibt es eine schönere und sauberere Möglichkeit, eine Zufallszahl mit einer bestimmten Wahrscheinlichkeit auszuwählen, wenn Sie eine große Anzahl von Wahrscheinlichkeiten berücksichtigen müssen?

Sicher. Schreiben Sie eine Funktion, die basierend auf einer Wahrscheinlichkeitstabelle eine Zahl generiert. Das ist im Wesentlichen die switch-Anweisung, auf die Sie hingewiesen haben: eine im Code definierte Tabelle. Sie könnten das gleiche tun mit Daten unter Verwendung einer Tabelle, die als eine Liste von Wahrscheinlichkeiten und Ergebnisse definiert ist:

 
probability outcome 
----------- ------- 
    0.4   1 
    0.2   2 
    0.1   3 
    0.15   4 
    0.15   5 

Jetzt können Sie eine Zahl zwischen 0 und 1 nach dem Zufallsprinzip auszuwählen. Beginnen Sie am Anfang der Liste, addieren Sie die Wahrscheinlichkeiten, bis Sie die von Ihnen gewählte Zahl überschritten haben, und verwenden Sie das entsprechende Ergebnis. Angenommen, die von Ihnen gewählte Nummer ist 0,6527637. Beginne oben: 0,4 ist kleiner, also mach weiter. 0,6 (0,4 + 0,2) ist kleiner, also mach weiter. 0,7 (0,6 + 0,1) ist größer, also hör auf. Das Ergebnis ist 3.

Ich habe die Tabelle hier aus Gründen der Übersichtlichkeit kurz gehalten, aber Sie können es so lange wie Sie möchten, und Sie können es in einer Datendatei definieren, so dass Sie nicht haben neu zu kompilieren, wenn sich die Liste ändert.

Beachten Sie, dass es Swift nichts Besonderes an dieser Methode gibt - Sie könnten dasselbe in C oder Swift oder Lisp tun.

+0

hinzugefügt habe Danke. Welche Struktur empfehlen Sie, den Tisch zu lagern? – asdf

+0

Sie benötigen eine geordnete Liste, verwenden Sie also ein Array. – Caleb

+1

Ich hatte nicht vor, Ihre Idee zu kopieren, aber ich hatte bereits den Code vorbereitet, den ich vorschlagen wollte :) –

0

Sie es mit exponentieller tun könnte oder quadratische Funktionen - habe x deine Zufallszahl, nimm y als die neue Zufallszahl. Dann müssen Sie nur die Gleichung wackeln, bis sie zu Ihrem Anwendungsfall passt. Sprich ich hatte (x^2)/10 + (x/300). Setzen Sie Ihre Zufallszahl ein (als Fließkomma-Form), und erhalten Sie dann das Wort mit Int(), wenn es herauskommt. Also, wenn mein Zufallszahlengenerator von 0 auf 9 geht, habe ich eine 40% ige Chance auf 0 und eine 30% ige Chance auf 1 - 3, eine 20% ige Chance auf 4 - 6 und eine 10% ige Chance auf an 8. Du versuchst im Grunde, eine Art Normalverteilung zu fälschen.

Hier ist eine Vorstellung davon, was es wie in Swift aussehen:

func giveY (x: UInt32) -> Int { 
    let xD = Double(x) 
    return Int(xD * xD/10 + xD/300) 
} 

let ans = giveY (arc4random_uniform(10)) 

EDIT:

ich nicht ganz klar oben war - was ich meinte, war Sie die Switch-Anweisung mit einer gewissen Funktion ersetzen könnte das würde eine Reihe von Zahlen mit einer Wahrscheinlichkeitsverteilung zurückgeben, die Sie mit Regression unter Verwendung von Wolfram oder etwas herausfinden könnten. Also, für die Frage, die Sie verbunden sind, könnten Sie etwas tun:

import Foundation 

func returnLevelChange() -> Double { 

    return 0.06 * exp(0.4 * Double(arc4random_uniform(10))) - 0.1 

} 

newItemLevel = oldItemLevel * returnLevelChange() 

Damit Funktion gibt eine doppelte irgendwo zwischen -0,05 und 2,1. Das wäre Ihre "x% schlechter/besser als die aktuelle Position". Da es sich jedoch um eine Exponentialfunktion handelt, wird es keine gleichmäßige Verteilung von Zahlen geben. Die arc4random_uniform (10) gibt ein int 0 bis 9, und jeder von denen in einem Doppel wie dies zur Folge hätte:

0: -0.04 
1: -0.01 
2: 0.03 
3: 0.1 
4: 0.2 
5: 0.34 
6: 0.56 
7: 0.89 
8: 1.37 
9: 2.1 

Da jede dieser Ints vom arc4random_uniform der zeigt sich die gleiche Chance hat, bekommt man Wahrscheinlichkeiten wie folgt:

40% chance of -0.04 to 0.1 (~ -5% - 10%) 
30% chance of 0.2 to 0.56 (~ 20% - 55%) 
20% chance of 0.89 to 1.37 (~ 90% - 140%) 
10% chance of 2.1   (~ 200%) 

Das ist etwas ähnlich zu den Wahrscheinlichkeiten, die andere Person hatte. Nun, für deine Funktion ist es viel schwieriger, und die anderen Antworten sind fast definitiv anwendbarer und eleganter. ABER du könntest es immer noch tun.

Ordne jeden Buchstaben in der Reihenfolge seiner Wahrscheinlichkeit an - vom größten zum kleinsten. Dann erhalten Sie ihre kumulativen Summen, beginnend mit 0, ohne die letzte. (also Wahrscheinlichkeiten von 50%, 30%, 20% wird 0, 0,5, 0,8). Dann multipliziert man sie mit hinreichender Genauigkeit zu ganzen Zahlen (0, 5, 8). Dann plotten Sie sie - Ihre kumulativen Wahrscheinlichkeiten sind Ihre x's, die Dinge, die Sie mit einer gegebenen Wahrscheinlichkeit (Ihre Buchstaben) auswählen möchten, sind Ihre y's. (Sie können natürlich keine tatsächlichen Buchstaben auf der Y-Achse zeichnen, also würden Sie ihre Indizes nur in einem Array darstellen). Dann würden Sie versuchen, dort eine Regression zu finden, und das wäre Ihre Aufgabe. Zum Beispiel jener Zahlen versuchen, bekam ich

e^0.14x - 1 

und dieses:

let letters: [Character] = ["a", "b", "c"] 

func randLetter() -> Character { 

    return letters[Int(exp(0.14 * Double(arc4random_uniform(10))) - 1)] 

} 

returns "ein" 50% der Zeit, "b" 30% der Zeit, und "c" 20 % der ganzen Zeit.Offensichtlich ziemlich umständlich für mehr Buchstaben, und es würde eine Weile dauern, um die richtige Regression herauszufinden, und wenn Sie die Gewichtungen ändern wollten, müssen Sie es manuell tun. Aber wenn Sie tat finden Sie eine schöne Gleichung, die Ihre Werte passierte, wäre die eigentliche Funktion nur ein paar Zeilen lang und schnell.

+0

Aber eine switch-Anweisung ist im Wesentlichen eine stückweise Funktion, so dass Ihre Wahrscheinlichkeitsverteilungsfunktion eine switch-Anweisung erfordert, die trotzdem beschrieben wird. – asdf

0

Basierend auf den Titel, dies für eine schamlose Werbung auf meine kleine Bibliothek ist eine gute Gelegenheit, swiftstats: https://github.com/r0fls/swiftstats

Die Antworten und Körper in Frage scheinen nicht meine Erwartung der Frage Titel zu passen, aber mein Projekt nicht mit dem Titel Anforderung helfen:

generieren von Zufallszahlen mit einer bestimmten Verteilung

Zum Beispiel diese erzeugen würden 3 zufällige Variablen aus einer Normalverteilung mit Mittelwert 0 und Varianz 1:

import SwiftStats 
let n = SwiftStats.Distributions.Normal(0, 1.0) 
print(n.random()) 

Unterstützten Verteilungen umfassen: normal, exponential, binomische, etc ...

es auch Datenanpassung Probe auf eine gegebene Verteilung unterstützt, die unter Verwendung von Maximum Likelihood Estimator für die Verteilung.

Siehe das Projekt Readme für weitere Informationen.

+0

Können Sie mich auf Anweisungen zum Erstellen von SwiftStats für iOS hinweisen? –

+1

@NathanBell nein, tut mir leid. Es ist auf der To-Do-Liste hier: https://github.com/r0fls/swiftstats#to-do Da Sie gefragt, werde ich versuchen, um es bald zu bekommen. Sie können gerne ein Problem dort in Github erstellen, wenn es Ihnen nichts ausmacht, mir nach der Erfüllung dieser Aufgabe mehr Zufriedenheit zu geben. Oder wenn Sie es selbst schaffen, würde ich eine PR auf der Readme oder Code lieben. Prost, Raphael – rofls

+1

du hast es! Vielen Dank! –

Verwandte Themen