Wie kann ich in Java am sinnvollsten eine Funktion für Wahrscheinlichkeiten schreiben. Also Prozentuale Chance das ein Ereignis ausgelöst wird?

3 Antworten

Vom Beitragsersteller als hilfreich ausgezeichnet

Denk nochmal drüber nach. In deinem Beispiel kommt die Ausgabe 3% Chance nur in 1% der Fälle, nämlich dann wenn 0,02 <= d < 0,03 ist.

Wenn du die else weglässt wird's richtig(er), aber dann können auch mehrere Ereignisse gleichzeitig auftreten.

Schließen sich die Ereignisse gegenseitig aus?


Schachpapa  20.07.2016, 18:10

Nein, weglassen der else tut's nicht, dann findet bei B immer auch A und bei C immer auch B und A statt.

Also dann eher so:

 if (d < 0.001){
System.out.print("0.1% chance!");
} else if (d < 0.001+0.02){
System.out.print("2% chance!");
} else if (d < 0.001+0.02+0.03){
System.out.print("3% chance!");
} else if (d < 0.001+0.02+0.03+0.9){
System.out.print("90% chance!");
}
0
Schachpapa  20.07.2016, 18:33
@Schachpapa

Und wenn du es dann richtig variabel haben willst, brauchst du eine
Liste von (ich nenne es mal) ActionObject, die du dann abklapperst:

interface ActionObject {
  double getProb();
void setProb(double p);
  void perform();
}
void distributeActions() {
double rnd = Math.random();
double rndCnt = 0.0;
for (ActionObject ao : listOfActionObjects) {
rndCnt += ao.getProb();
if (rnd < rndCnt) {
ao.perform();
break;
}
}
}

Für jedes Ereignis musst du allerdings eine eigene Klasse machen, z.B.:

class Explosion implements ActionObject {
double prob;
  double getProb() { return prob; }
void setProb(double p) { prob = p; }
  void perform() { System.out.println("Bumm"); }
}

Und deine Liste von ActionObjects machst du dann so:

List<ActionObject> listOfActionObjects = new ArrayList<>();
listOfActionObjects.add(new Explosion());
listOfActionObjects.add(new Vulkanausbruch());

Das ist jetzt nur so schnell "in die Tüte" gesprochen.

Details "zur Übung"

0
Xearox 
Beitragsersteller
 22.07.2016, 14:45
@Schachpapa

Ich habs mal so versucht. Auf dem ersten Blick scheint das so zu funktionieren.


ArrayList doubleArray = new ArrayList();

for(String input: args){
doubleArray.add(Double.parseDouble(input));
}



for(int i = 0; i < 100; i++){
double d = Math.random();
for(double dA : doubleArray){
if(d < (dA/100)){
System.out.print(d + "<" );
System.out.println(dA/100);
break;
}
}
}


Werde das ganze aber noch weiter ausbauen. So scheint es aber zu funktionieren.


Output bei 100 Durchläufen:

0.07261812964993042<0.08
0.03415858216837453<0.04
0.08132088766516476<0.09
0.06333821182586874<0.07
0.007268928408913244<0.01
0.05837609028649193<0.06
0.016289823910282886<0.02
0.07894219889649601<0.08
0.00449103134507689<0.01
0
Schachpapa  22.07.2016, 15:33
@Xearox

Ich nehme an, in der Kommandozeile übergibst du die gewünschten Wahrscheinlichkeiten? Also hier 1,2,4,6,7,8 und 9 %?

Wenn ich dich richtig verstanden habe möchtest du die Ausgabe "9%" in 9% der Ausgaben also etwa jedem 11. Fall erhalten, oder?

9% kommt aber bei deinem Beispiel nur ein Mal vor. Das ist bei 100 Versuchen weit weg von 9%, man würde neun Mal erwarten. Das liegt daran, dass du vorher schon viele Fälle abgefrühstückt hast, 0.341 fällt ja auch unter <0.9 ist aber schon bei <0.4 verarbeitet worden.

Du solltest es so machen:


void distributeActions() {
double rnd = Math.random();
double rndCnt = 0.0;
for (double dA : doubleArray) {
rndCnt += dA;
if (rnd < rndCnt) {
System.out.println(dA);
break;
}
}
}


(Das ist mein Beispiel von vorgestern ein wenig abgespeckt)

Wenn du das doubleArray mit 0.2 , 0.5 und 0.1 befüllst, sollte bei 1000 Aufrufen etwa 200 Mal "0.2", 500 Mal "0.5" und 100 Mal "0.1" erscheinen. Wenn du statt der Ausgabe den Wert mit return zurückgibst, kannst du es etwas bequemer zählen.

Aber:
Du kriegst dann nur den Wahrscheinlichkeitswert als Aus- bzw. Rückgabe und nicht einen String deiner Wahl.

Aber #2:
Du musst natürlich selbst darauf achten, dass die Einzelwahrscheinlichkeiten zusammen unter 100% bleiben (sonst wäre es keine gültige Verteilung).

0
Xearox 
Beitragsersteller
 22.07.2016, 17:47
@Schachpapa

Danke für deine Ausführliche Antwort, bekommst auf jedenfall einen Stern ;-)

Wenn ich dich richtig verstanden habe möchtest du die Ausgabe "9%" in 9% der Ausgaben also etwa jedem 11. Fall erhalten, oder? 

9% kommt aber bei deinem Beispiel nur ein Mal vor. Das ist bei 100 Versuchen weit weg von 9%, man würde neun Mal erwarten. Das liegt daran, dass du vorher schon viele Fälle abgefrühstückt hast, 0.341 fällt ja auch unter <0.9 ist aber schon bei <0.4 verarbeitet worden.

Also ich hab dich nicht richtig verstanden.

Ich habe vor ein paar Tagen, bevor ich hier die Frage gestellt habe, bereits einen anderen Test gemacht. Und ich finde, dass es dort eher ersichtlich ist.

Hier mal nen Pastebin dazu, gf.net lässt mich den Code, wieso auch immer, nicht einfügen. http://pastebin.com/P1Tjp8Vw

Als Ergebnis erhalte ich das hier:

Execution time is 41,02900 seconds

1% Chance dropped 20003583 times! 20003583/2000000000
2% Chance dropped 20002171 times! 20002171/2000000000
3% Chance dropped 19999286 times! 19999286/2000000000
90% Chance dropped 1740003733 times! 1740003733/2000000000

Die Wahrscheinlichkeit von 9% das es 11 mal droppt, muss ja nicht unbedingt richtig sein. Es kann auch möglich sein, das mit einer Wahrscheinlichkeit von 1% 30mal bei 100 Durchläufen dropped.

Ebenso kann 90% eben nur 2 mal bei 100 Durchläufen droppen.

Die Zahl wird ja zufällig bei jedem Durchlauf neu generiert.

0
Schachpapa  22.07.2016, 18:15
@Xearox

Das könnte natürlich zufällig so sein. Ist es aber hier nicht.

Auch bei deinem Beispiel oben sind 1% 2% und 3% ziemlich genau gleich häufig, selbst bei Rundung auf 3 Nachkommastellen kommt jedesmal 1,000% heraus. Das kann man bei 2 Milliarden Versuchen nicht mehr mit statistischer Streuung erklären. Und genausowenig, dass bei 90% exakt 87% herauskommt. Da ist ein systematischer Fehler drin, den ich dir schon seit meiner ersten Antwort zu erklären versuche.

Ich versuch's nochmal anders:

Die random-Funktion erzeugt eine pseudozufällige Zahl zwischen 0 und 1, dabei ist jeder Wert theoretisch gleich wahrscheinlich.

Wenn du deine Skala bei 0,01, bei 0,02 und bei 0,03 und bei 0,9 markierst, hast du 5 Trefferfelder: 1) links von 0.01, 2) zwischen 0.01 und 0.02, 3) zwischen 0.02 und 0.03, 4) zwischen 0.03 und 0.9 und 5) rechts von 0.9. Du wirst (statistisch) in 1% der Fälle in Feld 1 links von 0,01 landen und in 10% der Fälle in Feld 5 rechts von 0,9. Soweit so gut.

Aber das Feld 2 zwischen 0,01 und 0,02 ist auch nur 1% breit, deshalb sind 1% deiner Treffer zwischen 0.01 und 0.02. Da du alles unter 0.01 schon Feld 1 zuschlägst, kriegst du nur in 1% der Fälle Feld 2. Und mit der gleichen Logik 1% Feld 3 und 87% Feld 4. Das stimmt exakt mit deinem Experiment überein.

Probier mal das gleiche Experiment mit dem Code ganz oben unter "Also dann eher so". Das ist zwar nicht variabel, aber es funktioniert wenigstens ;-) Und wenn du das verstanden hast, ist der Schritt zu einer variablen Version nicht mehr schwer.

0
Schachpapa  22.07.2016, 18:30
@Schachpapa

Vielleicht ist das Ergebnis offensichtlicher, wenn du mit 30% und 60% arbeitest. Dann sollte ja der zweite Fall etwa doppelt so oft wie der erste eintreten. So wie du es implementiert hast, werden beide Fälle etwa gleich häufig sein.

Es kann natürlich sein, dass das Ganze so gemeint ist, wie es jetzt ist. Dann ist allerdings die Namensgebung ein wenig irreführend.

Und vielleicht noch etwas:

So wie es jetzt ist, schließen sich die Ereignisse gegenseitig aus. Sie könnten aber auch unabhängig sein, z.B. 30 % Sturm, 20% Vulkanausbruch. Dann treten ja in 0,2*0,3 = 6% der Fälle Sturm und Vulkanausbruch gleichzeitig ein. Dann musst du für jedes Ereignis eine eigene Zufallszahl erzeugen:

if Math.random() < 0.3:
print "Sturm"
if Math.random() < 0.2:
print "Vulkan"
0
Xearox 
Beitragsersteller
 22.07.2016, 18:40
@Schachpapa

Kann sein das ich mich vielleicht falsch ausgedrückt habe.

Was ich erreichen möchte ist folgendes:

Ich habe eine Datei welche wie folgt aufgebaut ist:

Event1;5%
Event2;6%
Event3;10%
Event4;2%
Event5;2%
Event6;6%
Event7;30%

Wenn nix davon ausgelöst wird, soll Event8 ausgelöst werden.

Was deine Annahme angeht, dass bei 30% und 60% der selbe Wert als Ergebnis ausgeworfen wird, ist korrekt.

Execution time is 50,20600 seconds

30% Chance dropped 599990077 times! 599990077/2000000000
60% Chance dropped 600002432 times! 600002432/2000000000

Ich werde mir daher deinen Tipp noch genauer anschauen und gebe dann Rückmeldung ;-)

0
Schachpapa  22.07.2016, 18:58
@Xearox

Was ich erreichen möchte ist folgendes:

Ich habe eine Datei welche wie folgt aufgebaut ist:

...

Und was genau soll dann passieren? Wenn man ein Programm schreibt, muss man vorher genau spezifizieren, was dabei herauskommen soll. Denn:

Wenn man nicht weiß, wohin man will, ist es egal wo man ankommt.

Soll jedes Event mit der angegebenen Wahrscheinlichkeit auftreten? Dürfen mehrere Events gleichzeitig auftreten?

Gibt es einen Rückgabewert (vielleicht die Nummer des Events oder eine Liste von Eventnummern) oder soll ein Text in die Konsole geschrieben werden?

0
Xearox 
Beitragsersteller
 22.07.2016, 19:14
@Schachpapa

Genau, so ungefähr.

Also den Events ist in meinem Programm bestimmte Methoden zugeordnet. Ich habs nur hier einfacher formuliert, da es sonst zu viel ist ;-)

Also machen wir es mit Nachrichten, welche an die Console ausgegeben wird. Wenn der User "Hallo" schreibt, soll das Programm mit einer Chance von, sagen wir mal, 

5% ein "Wie geht's dir" ausgeben. 

Mit einer Chance/Wahrscheinlichkeit von 1% soll der User die Antwort "Schreib jemand anderen Hallo" zurück bekommen. 

Mit einer Chance/Wahrscheinlichkeit von 5% soll der User die Nachricht "Hallo User" zurück bekommen.

Mit einer Chance/Wahrscheinlichkeit von 20% soll der User die Nachricht "Ist das Wetter heute nicht schön?" zurück bekommen.

Wenn von den davon gar nichts ausgelöst wird, also in allen anderen Fällen, soll der User einfach nur ein "Hallo" zurück bekommen.

Wie gesagt, das ist nur ein Beispiel, in meinem Programm ist es ein wenig komplexer, vor allem die Methoden sind länger und erstrecken sich auf über 1000 Zeilen Code.

Ich könnte natürlich auch einen Random von Ganzen Zahlen erzeugen, welche dann 100% Zufällig ausgewählt werden. Sprich das ganze in ein switch case packen. 

Aber eben das möchte ich nicht :D Ich möchte halt angeben, wie hoch die Wahrscheinlichkeit ist, das eben Event1 ausgelöst wird.

Wichtig dabei ist es nur, dass die %-Angaben auch mehrfach vor kommen können, also das Events auch 10 x 5% erhalten. Diese werden dann einfach zusammen gefasst und dann dort Zufällig was ausgesucht, wie ich das oben mit dem switch case geschrieben habe.

0
Schachpapa  22.07.2016, 19:41
@Xearox

Dann habe ich dich von Anfang an richtig verstanden (keine Mehrfachereignisse) und empfehle dir meinen Lösungsansatz ;-)

In der Schleife solltest du entweder die aktuelle Eventnummer mitzählen oder du benutzt gleich die klassische for-Schleife:


int distributeActions(double[] doubleArray) {
/*
* liefert eine zufällige Ereignisnummer
* gemäß der in doubleArray gegebenen
* Wahrscheinlichkeitsverteilung
* Die Werte in doubleArray müssen zusammen
* kleiner als 1 sein
* Anm:
* doubleArray sollte besser distribution heißen
*/
double rnd = Math.random();
double rndCnt = 0.0;
int elseWert = doubleArray.length;


for (int i=0; i < doubleArray.length; i++) {
rndCnt += doubleArray[i];
if (rnd < rndCnt) {
System.out.println(i); // ggf weglassen
return i;
}
}
return elseWert;
}



bei ArrayList verwendest du
dA.get(i) statt dA[i] und dA.size() statt dA.length

PS: Danke für den Stern

0

Schau mal hier:

https://github.com/pheek/WalkerCategories

Die Lösung mit "if/else if/else if/else if/...." kann bei vielen Prozentzahlen sehr unperfomant werden. Nehmen wir an, Du hast 10 Kategorien, dann musst Du im Schnitt fünf Vergleiche anstellen. Mit dem Algorithmus von Walker (s. Knuth «The art of Computer Programming» Band II) aus dem Jahre 1977 braucht es im Wesentlichen gerade mal eine Multiplikation, ein Vergleich und ein Nachschlagen im Array, egal wie viele Kategorien an Prozentzahlen Du verwendest! Das Beispiel (obiger Link, dann "Example.java" zeigt, wie die Klasse WalkerCategories angewendet werden kann.


Schachpapa  26.07.2016, 12:32

Das ist ja eine feine Sache. Muss ich mir mal näher anschauen.

0

Nur die Chancen variabel?

Textdatei: 1,2,3,5 (alle Angaben in %)

Diese Datei Parsen und zB in ein Array schreiben und schon durch 100 teilen.
Dann halt 



double d = Math.random();
if (d <= array[0]){
System.out.print("0.1% chance!");
} else if (array[0] < d && d <= array[1]){
System.out.print("2% chance!");
} else if (array[1] < d && d <= array[2]){
System.out.print("3% chance!");
} else if (array[2] < d && d <= array[3]){
System.out.print("90% chance!");
}




Xearox 
Beitragsersteller
 20.07.2016, 17:51

Ich versuchs mal =) Danke dir erstmal.

0