Entwickler-Ecke

Algorithmen, Optimierung und Assembler - Gewichteter Zufallsgenerator


VampireSilence - Sa 27.11.10 23:36
Titel: Gewichteter Zufallsgenerator
Ich sitze gerade dabei einen Zufallsgenerator zu schreiben. Dieser soll aber keine statistische oder chaotische Verteilung der Ergebnisse aufweisen, sondern eine systematisch gewichtete. Und eigentlich geht es dabei viel mehr um einen logischen Denkanstoß, als um C# selber.

Ich versuche das Ganze mal mit einem Beispiel zu konkretisieren.
Ausgangswert für den Generator ist ein Array, dass beispielsweise so aussehen könnte:

Grün = 10
Rot = 30
Blau = 60
Schwarz = 125
(realisiert über einen Hashtable)

Und wenn ich den Generator jetzt 50 Millionen mal laufen lassen, will ich dass die Ergebnisse später annähernd im Verhältnis 10:30:60:125 (Grün zu Rot zu Blau zu Schwarz) stehen. Ich erwarte auch keinen kompletten Code von euch, sondern eben nur einen kleinen Denkanstoß. Vielleicht lässt sich das Ganze ja auch irgendwie mathematisch lösen. Ich steh grad aufm Schlauch.

mfg
- VampireSilence


Moderiert von user profile iconKha: Topic aus Basistechnologien verschoben am So 28.11.2010 um 13:11


huuuuuh - Sa 27.11.10 23:58

hab ich selber letztens überlegt. bin zu diesem schluss gekommen:
du hast ja 4 möglichkeiten, nun generierst du eine zufallszahl zwischen 0 und 225 (10+30+60+125). alle ergebnisse zwischen 0 und 10 zählen für Grün, alle zwischen 10 und 40 für rot usw.
hoffe ich hab richtig gedacht, habs noch nie ausprobiert :D


Delete - So 28.11.10 00:09

Hier eine Demo in Delphi: http://www.michael-puff.de/Programmierung/Delphi/Demos/


Christian S. - So 28.11.10 10:49

Michael,

es ist vielleicht nicht schlecht, wenn Du außer einem Link auch noch ein paar erklärende Worte zum Algorithmus findest. Es ist doch immer schön, wenn man die grundlegenden Infos direkt im Forum und nicht extern hat :-)

Grüße
Christian


Horst_H - Mo 29.11.10 10:23

Hallo,

Mit Luckies Version kann ich nichts anfangen, wozu das hier gut sein soll, das der schon gezogene unwahrscheinlicher wird.( OK, isch hann kopp-ping aber wo sind die Gewichte für mehrfaches Vorkommen ? )
Es geht dort um etwas ganz anderes.
Zitat:

Gewichteter Zufall - Readme
Copyright Michael Puff

http://www.michael-puff.de
mail@michael-puff.de

Bedienung:
Dem Programm wird eine Namensliste in einer Textdatei übergeben. Beim
Programmstart wird diese auf dem Bildschirm ausgegeben. Durch drücken
der Return-Taste wird die Namensliste erneut ausgegeben und der zu-
fällig gewählte Namen in der Liste markiert. In den folgenden Zie-
hungen wird der zuvor gezogene Name an den Anfang der Liste gesetzt
bevor erneut ein Name gezogen wird.

Algorithmus:
Damit schon mal gezogene Namen mit geringerer Wahrscheinlichkiet ge-
zogen werden, wird die Zufallszahl zum Bestimmen des Indexes in der
Liste erhöht und der in der letzten Runde gewählte Name an den An-
fang der Liste gesetzt. Da die Wahrscheinlichkeit höher ist, dass
Namen vom Ende der Liste gezogen werden, werden noch nicht gezogen-
en Namen "bevorzugt".

- Ermitteln einer Zufallszahl zwischen 0 und 1
- Ziehen der Wurzel aus der Zufallszahl
- Multplizieren der Zufallszahl mit der Anzahl der Namen
- Addieren von 1
- Abschneiden des Nachkommaanteiles

Durch den Wurzelexponenten kann man bestimmen, wie häufig schon mal
gezogenen Namen noch mals gezogen werden sollen. Je höher der Wurzel-
exponent, desto geringer ist die Wahrscheinlichkeit, da die zufällig
ermittelte Zahl mit höherer Wahrscheinlichkeit einen Namen am Ende+++
der Liste wählt.


die Version von huuuuuh ist doch schon passend, wobei 0..9 10 Werte sind.
Das lässt sich noch einfacher umsetzen.
Man erzeugt ein Zählerfeld mit einer Länge der Anzahl verschiedenen Farben/Merkmale.
Ein Auswahlfeld mit einer Länge von hier 225.
In dieses Auswahlfeld wird dann der passende Index eingetragen also von 0..9 der Index auf Zählerfeld Grün= 0, von 10..40 ( linkse Seite+Anzahl -1 = 10 + 30 - 1) für Rot = 1.

Dann erzeugt man sich nur noch Zufallszahlen im Bereich von 0..225-1 und
erhöht dann ZaehlerFeld[AuswahlFeld[ganzzahlige Zufallszahl aus 0..224]].

Das Auswahlfeld kann vielleicht verkleinern, wenn man das alle Zahlen durch deren GGT 5 teilt.
statt 10/30/60/125 eben 2/6/12/25.
Aber wie macht man es geschickt, wenn es Werte wie sqrt(0.5) oder 2/pi() sind.
Auf die Schnelle fiele mir nur eine Sonderbeandlung ein, bei der im Auswahlfeld an an einer Grenzstelle zum Beispiel -1 steht und man entsprechend reagiert.
Sei Grün mit einer Wahrscheinlichkeit 2/pi() = 0,6366.. und Rot eben 1-2/pi().
Dann kann man mit Kettenbruch http://www.arndt-bruenner.de/mathe/scripts/bruchrechnung1.htm eine Näherung der Aufteilung finden:
2/pi()= 226/355.Mist ist schon auf 1e-8 genau.
Also wählte man eine Feld von Länge 355 und alle Werte bis 226-1 zählen für Grün, die anderen für Rot.

Gruß Horst


Delete - Mo 29.11.10 10:36

Der Zufall ist schon gewichtet. Wie du schon gesagt hast, wird eine schon mal gezogene Person mit einer geringeren Wahrscheinlichkeit wieder gezogen, als eine Person die noch nicht gezogen wurde.

@Christian: Es liegt eine Readme bei.


Christian S. - Mo 29.11.10 10:42

user profile iconLuckie hat folgendes geschrieben Zum zitierten Posting springen:
@Christian: Es liegt eine Readme bei.
Womit die Information immer noch extern ist :nixweiss:


Delete - Mo 29.11.10 10:43

Aber jetzt wurde sie ja gepostet. ;)


Kha - Mo 29.11.10 11:27

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Dann kann man mit Kettenbruch http://www.arndt-bruenner.de/mathe/scripts/bruchrechnung1.htm eine Näherung der Aufteilung finden:
:shock: Das ist etwas zu kompliziert gedacht :) .
Da jedes p aus P die Wahrscheinlichkeit p/sum(P) besitzt , einfach eine Zahl 0<=X<=sum(P) auswürfeln lassen und schauen, welche Partialsumme p1 + p2 + ... + pn sie übersteigt. n ist dann der gesuchte Index.
Das funktioniert komplett ohne zusätzlichen Speicher und problemlos auch mit reellwertigen Zahlen.


Horst_H - Mo 29.11.10 11:29

Hallo,

wie bekomme ich denn sinnvoll die Aufteilung im Verhältnis 2/6/12/25 in Luckies Variante, das ist mir nicht offensichtlich.
Auserdem ist mir nicht klar, was das Programm eigentlich vollbringen soll.
Ich ziehe einen Namen und mache dessen Auswahl "und" seiner Nachbarn doch unwahrscheinlicher, weil ich diese nach vorne in der Liste verschiebe und die lineare Verteilung der Zufallszahlen in eine 1/kubische ( Exponent = 1/3 im Programm) umwandle, dass heisst werte von 0.79 .. 1 haben die Wahrscheinblichkeit von 0.5.
Dieses passiert bei jeder Ziehung.
Es erschliesst sich mir nicht, was man damit sinnvolles machen kann. Der erstgezogene Name wandert langsam bergauf, bis er wieder gezogen wird.
Man ändert ja ständig die Position, funktioniert das dan überhaupt noch mit der Wahrscheinlichkeit.
Letztendlich müssten bei dem Programm alle Namen gleichmaässig gezogen werden.

Gruß Horst


Delete - Mo 29.11.10 11:45

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Auserdem ist mir nicht klar, was das Programm eigentlich vollbringen soll.

Ok, das ist in der Berufsschule entstanden. Und zwar hatte der Lehrer eine Fragerunde gemacht um uns zu testen wie gut wir den Stoff beherrschen. Wenn man jetzt eine ungewichtete Wahrscheinlichkeit nimmt, kann es sein, dass zufällig ein paar Schüler immer wieder dran kommen und einige gar nicht.

Durch den Algorithmus wird die Wahrscheinlichkeit nach "oben" in der Liste verschoben. Und dadurch, dass schon gezogene Schüler ans Ende der Liste wandern, wird die Wahrscheinlichkeit für noch nicht gezogene Schüler höher, da sie nach "oben" wandern.
Zitat:
Letztendlich müssten bei dem Programm alle Namen gleichmaässig gezogen werden.

Und genau das wird dadurch eben wahrscheinlicher.


Horst_H - Mo 29.11.10 12:55

Hallo,

@Kha:
Du hast ja recht, mir dem Vergleich.
Ein Feld mit den Namen von 0..n-1 // 0..3 = grün, rot, blau, schwarz
Ein Feld mit den aufsummierten Wahrscheinlichkeiten p[0..n-1] = 10/225,(10+30)/225,(10+30+60)/225, 1

Dann musst Du immer solange abfragen, bis das passende Feld gefunden ist und der gefundene Index gehört dann zum entsprechenden Namen.
Und dieses abfragen wollte ich mir sparen und hoffte, es wäre damit schneller, als ein Vergleich von mehreren Fliesskommazahlen, statt ein indirekter Zugriff.
Natürlich kann man auch mit binärer Suche im Feld p suchen., aber das braucht sicher keiner.

Gruß Horst


Xion - Mo 29.11.10 14:50

http://www.delphi-forum.de/topic_Wuerfelspiel++Programm+fuer+Facharbeit_99225,20.html

In dem Programm kannst du Gewichtungen einstellen, der Source ist dabei. Die Technik ist die von user profile iconhuuuuuh geschilderte


Horst_H - Mo 29.11.10 15:26

Hallo,

nur um zu zeigen, dass es mit dem Feldzugriff zügiger ist, als die Summenwahrscheinlichkeit abzuklappern.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
program Nichts;

//freepascal 2.4.2 
{$IFDEF FPC}
  {$Mode delphi}
  {$R-}
  {$I-}
  {$V-}
  {$OPTIMIZATION RegVar}
  {$OPTIMIZATION Peephole}
  {$OPTIMIZATION CSE}
  {$OPTIMIZATION ASMCSE}      
{$ENDIF}  
{$AppType console}

uses
  sysutils;

const
  cZiehungen = 22500000;
  cAnzahlNamen = 4;
  cAnzahlNamen1 = cAnzahlNamen-1;

type
  tIndexNamen = 0..cAnzahlNamen1;
  tNamenFeld = array[tIndexNamen] of string[10];
  tZaehlFeld = array[tIndexNamen] of longint;
  tIndexWahrschFeld = array[tIndexNamen] of double;

const
  cNamen   : tNamenFeld = ('gruen','rot','blau','schwarz');
  cAnteile : tZaehlFeld = (10,30,60,125);//(2,6,12,25);

var
  IndexFeld : array of tIndexNamen;
  ZaehlFeld : tZaehlFeld;
  IndexWahrschFeld : tIndexWahrschFeld;
  T1,T0 : TDateTime;
  SumAnteile,i : longint;

procedure ZaehlfeldLoeschen;
var
  i : longINt;
begin
  For i in  tIndexNamen do
    ZaehlFeld[i] := 0;
end;  

function GesamtAnteile: longInt;
var
  i : longInt;
begin
  result := 0;
  For i in tIndexNamen do
    result := result+cAnteile[i];
end;
  
procedure IndexFeldAufbauen;
var
  i,j,k : longint;
begin
  setlength(IndexFeld,SumAnteile);
  k := 0;
  For i in tIndexNamen do 
    For j := 1 to cAnteile[i] do
      begin
      IndexFeld[k] := i;
      k := k+1;
      end;
end;

procedure IndexWahrschFeldAufbauen;
var
  i,k : longint;
begin
  k := 0;
  For i in tIndexNamen do 
    begin
    k := k + cAnteile[i];
    IndexWahrschFeld[i] := k / sumAnteile;
    end;
end;

procedure ZaehlFeldAusgabe;
var
  i : LongInt;
begin
  For i in tIndexNamen do
    writeln(cNamen[i]:10,ZaehlFeld[i]:11,ZaehlFeld[i]/cZiehungen*sumAnteile:10:3);
end;

procedure IndexZiehungen;
var
  i : longint;
begin
  ZaehlfeldLoeschen;
  For i := 1 to cZiehungen do
    inc(Zaehlfeld[IndexFeld[random(SumAnteile)]]);

  ZaehlFeldAusgabe;
end;

procedure IndexWahrschFeldZiehungen;
var 
  i,j: longint;
  p : double;
begin
  ZaehlfeldLoeschen;
  For i := 1 to cZiehungen do
    begin
    p := random;
    For j in tIndexNamen do 
      if p < IndexWahrschFeld[j] then
        break;
    inc(ZaehlFeld[j]);    
    end;
  ZaehlFeldAusgabe;
end;

begin
sumAnteile := GesamtAnteile;

// Cool and quite abschalten 
for i := 0 to 1000000 do 
  begin
  IndexFeldAufbauen;
  IndexWahrschFeldAufbauen;
  end;

randseed := 47110815;//randomize;
writeln('IndexFeld');
T0 := time;
IndexFeldAufbauen;
IndexZiehungen;
T1 := time;
writeln;
writeln(FormatDateTime('hh:mm:ss.zzz',T1-T0)); 

randseed := 47110815;
writeln('Wahrscheinlichkeitsfeld');
T0 := time;
IndexWahrschFeldAufbauen;
IndexWahrschFeldZiehungen;
T1 := time;
writeln;
writeln(FormatDateTime('hh:mm:ss.zzz',T1-T0)); 

end.
{Ausgabe:
IndexFeld
     gruen    1000930    10.009
       rot    2999414    29.994
      blau    5999577    59.996
   schwarz   12500079   125.001

00:00:00.609
Wahrscheinlichkeitsfeld
     gruen    1000930    10.009
       rot    2999414    29.994
      blau    5999577    59.996
   schwarz   12500079   125.001

00:00:01.107
Drücken Sie eine beliebige Taste . . .}


ist bei mir also fast doppelt so schnell

Gruß Horst

Anbei ein Bild von Xion's Version zu der Ausgangsfrage


Delphi-Laie - Mo 29.11.10 16:46

user profile iconhuuuuuh hat folgendes geschrieben Zum zitierten Posting springen:
du hast ja 4 möglichkeiten, nun generierst du eine zufallszahl zwischen 0 und 225 (10+30+60+125). alle ergebnisse zwischen 0 und 10 zählen für Grün, alle zwischen 10 und 40 für rot usw.


Das ließe sich kürzen. Das kleinstmögliche Quadrupel, bei dem alle Zahlen noch ganzzahlig sind, wäre: 2-6-12-25, als Summe: 45.


VampireSilence - Mo 29.11.10 20:16

Also die Variante von huuuuh kommt am Nächsten an mein Problem heran. Das Ganze liefe also quasi wie bei einem Roulette. Als vereinfachende Randbedingung sei allerdings gesagt, dass ich die Gewichtung später manuell erstellen werden und es sich dabei nur um Ganzzahlen handeln wird. Einen Verglich zwischen 1 : Pi würde ich dann einfach mit 100 zu 314 Nähern, das wäre exakt genug. Was mir nur ein bisschen Sorgen macht, ist die Performance, denn es werden nachher eine Menge "Farben" (um mein Beispiel mal aufzugreifen) sein und ich müsste dementsprechend viele Indizes überprüfen, um zu einem Ergebnis zu kommen. Und das sollte am besten keine Minute dauern, wenns geht. Vielleicht ist es auch eine Überlegung wert, das Ganze zu assemblieren, aber derweil bastle ich jetzt erstmal noch dran rum, vielleicht finde noch ne bessere Lösung.

mfg
- VampireSilence


Horst_H - Mo 29.11.10 21:31

Hallo,

liest hier jemand mal etwas komplett bis zum Ende durch ?? ;-) ( Tue ich auch nicht immer )

Wenn Du meinen ObjektPascal-Quelltext des "progam Nichts" mal zu Ende scrollst, steht dort am Ende die Laufzeit für beide Versionen.( AMD Athlon 245 X2 2.9 Ghz )
cZiehungen = 22500000;// -> 22,5 Millionen
Kha-Version 1,1 Sekunden -> 117 CPU-Takte
hu..uh-Version 0,61 Sekunden -> 78 CPU-Takte // fast alles nur random ( > 70 CPU-Takte arbeitet mit Division )

Da der Zufallszahlengenerator von Delphi2006ff erheblich! schneller ist, könnte der von C# vielleicht auch derart gut sein.

Um wieviele Farben soll es den gehen?

Gruß Horst


Xion - Mo 29.11.10 22:25

Angenommen du hast 6 Farben.

Delphi-Quelltext
1:
2:
3:
4:
Cls: array of integer; //hier sind die Gewichtungen drin
...
Cls[3]:= 17//Gewichtungen setzen (1x)
...

R: array of integer; //BinSuche array

// Aufsummierung berechnen (1x)

Delphi-Quelltext
1:
2:
3:
4:
R[0]:=Cls[0];
R[1]:=R[0]+Cls[1];
R[2]:=R[1]+Cls[2];
...

Die Arrays sehen dann so aus:

Delphi-Quelltext
1:
2:
Cls: 1 4 30 17 101 401 
R  : 1 5 35 52 153 554

Als Zufallszahl ziehen wir dann z.B:

x = 400

Binäre Suche:

Delphi-Quelltext
1:
2:
3:
R[2] = 35  < 400 //in der Mitte zuerst gucken
R[4] = 153 < 400 //in der Mitte rechts gucken
R[5] = 554 > 400 -> Farbe Nummer 4 ist die gesucht.

Das ganze nennt sich Binäre Suche und ist O(log(n)) schnell.

Hast du 1.000.000 Farben, braucht das log(1.000.000) = log(2^20) => 20 Vergleiche . Hübsch, gell?


Horst_H - Mo 29.11.10 22:35

Hallo,

O.K hier dann array of integer statt array of double ( da ging es ja um 2/pi() als Grenze) macht Sinn,.
Werde ich mal testen.
Der Vorschlag binäre Suche gab es schon um 11:35 Uhr ...

Gruß Horst
Edit:
Ausgabe mit p : array of longint statt double und 6 Farben immer noch 22,5 Mio Ziehungen.


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
IndexFeld
     gruen     616907    10.008
       rot    1850633    30.021
      blau    3697190    59.977
   schwarz    7702419   124.950
      gelb     801646    13.004
     weisz    7831205   127.040

Laufzeit 00:00:00.605
Wahrscheinlichkeitsfeld
     gruen     616907    10.008
       rot    1850633    30.021
      blau    3697190    59.977
   schwarz    7702419   124.950
      gelb     801646    13.004
     weisz    7831205   127.040

Laufzeit 00:00:00.877
Drücken Sie eine beliebige Taste . . .


Xion - Mo 29.11.10 22:41

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Der Vorschlag binäre Suche gab es schon um 11:35 Uhr ...


Garnicht gesehen so klein und versteckt wie das da stand :oops: (11:55 war das)


Horst_H - Mo 29.11.10 22:48

Hallo,

mit meien -17 Dioptrien benutze ich im Browser gerne STRG - + ;-)

Gruß Horst


Xion - Mo 29.11.10 22:54

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Ausgabe mit p : array of longint statt double und 6 Farben immer noch 22,5 Mio Ziehungen.


Nee, bei 6 Farben bringt das ja fast garnix. Nehm mal 1000 Farben oder so. Je größer desto besser ;)


Horst_H - Di 30.11.10 00:25

Hallo,


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
program Nichts;
{$IFDEF FPC}
  {$Mode delphi}
  {$R-}  
  {$I-}
  {$V-}
  {$OPTIMIZATION RegVar}
  {$OPTIMIZATION Peephole}
  {$OPTIMIZATION CSE}
  {$OPTIMIZATION ASMCSE}      
{$ENDIF}  
{$AppType console}

uses
  sysutils;

const
  cZiehungen = 1000*1000*10;
  cAnzahlNamen = 2000000;
  cAnzahlNamen1 = cAnzahlNamen-1;

type
  tIndexNamen = 0..cAnzahlNamen1;
  tIndexNamen_1 = 0..cAnzahlNamen;
  tNamenFeld = array[tIndexNamen] of string[10];
  tZaehlFeld = array[tIndexNamen] of longint;
  tIndexWahrschFeld = array[tIndexNamen_1] of LongInt;

// cNamen   : tNamenFeld = ('gruen','rot','blau','schwarz','gelb','weisz');

var
  Anteile : tZaehlFeld;
  IndexFeld : array of tIndexNamen;
  ZaehlFeld : tZaehlFeld;
  IndexWahrschFeld : tIndexWahrschFeld;
  T1,T0 : TDateTime;
  SumAnteile,i : longint;

procedure ZaehlfeldLoeschen;
var
  i : longINt;
begin
  For i in  tIndexNamen do
    ZaehlFeld[i] := 0;
end;  

function GesamtAnteile: longInt;
var
  i : longInt;
begin
  result := 0;
  For i in tIndexNamen do
    result := result+Anteile[i];
end;
  
procedure IndexFeldAufbauen;
var
  i,j,k : longint;
begin
  setlength(IndexFeld,SumAnteile);
  k := 0;
  For i in tIndexNamen do 
    For j := 1 to Anteile[i] do
      begin
      IndexFeld[k] := i;
      k := k+1;
      end;
end;

procedure IndexWahrschFeldAufbauen;
var
  i,k : longint;
begin
  k := 0;
  IndexWahrschFeld[0] := 0;
  For i in tIndexNamen do 
    begin
    k := k + Anteile[i];
    IndexWahrschFeld[i+1] := k;// / sumAnteile;
    end;
end;

procedure ZaehlFeldAusgabe;
var
  i : LongInt;
begin
//  For i in tIndexNamen do
  i := high(tIndexNamen);
    writeln(ZaehlFeld[i]:11,ZaehlFeld[i]/cZiehungen*sumAnteile:9:3,Anteile[i]:5);
end;

procedure IndexZiehungen;
var
  i : longint;
begin
  ZaehlfeldLoeschen;
  For i := 1 to cZiehungen do
    //random(SumAnteile);
    inc(Zaehlfeld[IndexFeld[random(SumAnteile)]]);
end;

procedure IndexWahrschFeldZiehungen;
var 
  i,o,u , m: longint;
  p : longint;
begin
  ZaehlfeldLoeschen;
  For i := 1 to cZiehungen do
    begin
    p := random(sumAnteile);
    //binaere Suche
    o := cAnzahlNamen;
    u := 0;
    repeat
      m := (o+u) div 2;
      if p >= IndexWahrschFeld[m] then
        u := m+1
      else 
        begin
        if  (p >= IndexWahrschFeld[m-1])  then
           break    
        else
          o := m-1;  
        end;  
    until o< u;
    
    inc(ZaehlFeld[m-1]);    

    end;
end;

procedure AnteileBelegen;
var 
  i : longint; 
begin
  For i in tIndexNamen do
    Anteile[i] := random(116)+10
end;


begin
randseed := 47110815;
AnteileBelegen;
sumAnteile := GesamtAnteile;
Writeln('Anzahl Farben      ',cAnzahlNamen:12);
Writeln('Summe aller Anteile',sumAnteile:12);
Writeln('Anzahl Ziehehungen ',cZiehungen:12);

Writeln;
// Cool and quite abschalten 
for i := 0 to 65536 div cAnzahlNamen do 
  begin
  IndexFeldAufbauen;
  IndexWahrschFeldAufbauen;
  end;

randseed := 47110815;//randomize;
writeln('IndexFeld');
T0 := time;
IndexFeldAufbauen;
IndexZiehungen;
T1 := time;
ZaehlFeldAusgabe;
writeln;
writeln('Laufzeit ',FormatDateTime('hh:mm:ss.zzz',T1-T0)); 

randseed := 47110815;
writeln('Wahrscheinlichkeitsfeld');
T0 := time;
IndexWahrschFeldAufbauen;
IndexWahrschFeldZiehungen;
T1 := time;
ZaehlFeldAusgabe;
writeln;
writeln('Laufzeit ',FormatDateTime('hh:mm:ss.zzz',T1-T0)); 

end.


Ausgabe:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
Anzahl Farben           2000000
Summe aller Anteile   135016937
Anzahl Ziehehungen     10000000

IndexFeld
          3   40.505   65

Laufzeit 00:00:02.871
Wahrscheinlichkeitsfeld
          3   40.505   65

Laufzeit 00:00:06.240

Obwohl es jetzt 2 Mio Farben sind und ein IndexFeld von über 600 Mb entsteht ist diese Version immer noch schneller.
Die Ausgabe: 3 40.505 65
besagt, dass die letzte Farbe 3 mal gezogen wurde umgerechnet 40.505 statt der erwarteten 65, aber es zeigt, das beide Verfahren exakt die gleiche Ausgabe haben.Bei 31 Farben lies sich das ja noch komplett sehen.

Gruß Horst

Edit:
Mit einer anderen random Funktion http://www.delphipraxis.net/134097-random-purepascal.html ist es schneller.
Dies verändert aber die Zahlenfolge.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
var
  myrandseed: cardinal;
function myrandom(range: cardinal): cardinal;
begin
  myrandseed := myrandseed * $08088405 + 1;
  result := (int64(range) * myrandseed) shr 32;
end;

Wieder 2 Mio Farben und 10 Mio Ziehungen,


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Anzahl Farben           2000000
Summe aller Anteile   134948857
Anzahl Ziehungen     10000000

IndexFeld
          6   80.969   94

Laufzeit 00h00m02.006s
Wahrscheinlichkeitsfeld
          6   80.969   94

mittlere binaere Suchtiefe 19.9512
Laufzeit 00h00m05.888s

Wenn alles im level1 Cache ist bei 10e8 Durchläufen -> 29 CPU-Takte bei Indexzugriff statt 76.


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
Anzahl Farben               255
Summe aller Anteile       17274
Anzahl Ziehungen    100000000

IndexFeld
     207817   35.898   36
Laufzeit 00h00m00.965s

Wahrscheinlichkeitsfeld
     207817   35.898   36
mittlere binaere Suchtiefe  7.0225
Laufzeit 00h00m06.976s


VampireSilence - Mi 01.12.10 16:15

So, ich habe jetzt mal bei der Herangehensweise von Xion angesetzt und das Ganze als Bisektionsverfahren umgesetzt. Ich habe auch einen Benchmark geschrieben (siehe Code weiter unten) und beide Methoden darin verglichen.

Zum Vergleich habe ich verschiedene int[n] (n: Farben) verglichen, wobei ich die Gewichtung jeweils auf int[n] = n^2 festgelegt habe. Aufgrund der int.MaxValue ergab sich daraufhin auch das Maximum von 1860 Farben. Darüber ist ein Überlauf der Maxima aufgetreten.

Was sich ergeben hat war, dass die Binärsuche (kurz "Scan" genannt) bis n=10 noch geringfügig schneller ist. Bei n=11 und darüber hinaus ist das Bisektionsverfahren schneller (um n=1500 herum [also in dem Bereich, wo es für mich relevant wird], ist das Bisektionsverfahren ganze 34-mal schneller, als der Scan). Die Klasse ("WeightedChance") habe ich dann noch in eine Klassenbibliothek extrahiert und kompiliert, was einen zusätzlichen Geschwindigkeitsschub einbrachte.


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
n, Scan [Run/s], Bisect [Run/s]
1, ~5300k, ~4700k
10, ~2750k, ~2700k
100, ~530k, ~1900k
200, ~270k, ~1700k
500, ~113k, ~1600k
1000, ~56k, ~1450k
1500, ~38k, ~1300k

Gemittelt über: 1.000.000 Berechnungen

Testsytem: Windows 7, Energiesparmodus (Firewall + AVPersonal aktiv), Intel(R) Atom(TM) CPU N450 @ 1.66 GHz

Code: frmMain.cs

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WeightedChance
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();
        }

        private void BenchBisect()
        {
            int _try;

            if (!int.TryParse(this.txtColors.Text, out _try))
            {
                return;
            }

            int[] Chances = new int[int.Parse(this.txtColors.Text)];

            for (int i = 0; i < Chances.Length; i++)
            {
                Chances[i] = (int)(Math.Pow(i + 12.0) % int.MaxValue);
            } 
            
            WeightedChance wc = new WeightedChance(Chances);
            int runs = 1000000;

            this.txtResult.Text = "Sei i(k) = k^2.";

            DateTime start = DateTime.Now;

            for (int i = 0; i < runs; i++)
            {
                wc.NextBisect();
            }

            DateTime end = DateTime.Now;

            this.txtResult.Text += "\r\n" + end.Subtract(start).TotalMilliseconds + " ms needed.\r\n" + (int)(runs / end.Subtract(start).TotalSeconds) + " Runs per Seconds";
        }

        private void Benchmark()
        {
            int _try;

            if (!int.TryParse(this.txtColors.Text, out _try))
            {
                return;
            }

            int[] Chances = new int[int.Parse(this.txtColors.Text)];

            for (int i = 0; i < Chances.Length; i++)
            {
                Chances[i] = (int)(Math.Pow(i + 12.0) % int.MaxValue);
            }

            WeightedChance wc = new WeightedChance(Chances);
            int runs = 1000000;

            this.txtResult.Text = "Sei i(k) = k^2.";

            DateTime start = DateTime.Now;

            for (int i = 0; i < runs; i++)
            {
                wc.Next();
            }

            DateTime end = DateTime.Now;

            this.txtResult.Text += "\r\n" + end.Subtract(start).TotalMilliseconds + " ms needed.\r\n" + (int)(runs / end.Subtract(start).TotalSeconds) + " Runs per Seconds";
        }

        private void btnCheck_Click(object sender, EventArgs e)
        {
            this.Benchmark();
        }

        private void btnCheckBisect_Click(object sender, EventArgs e)
        {
            this.BenchBisect();
        }
    }

    public class WeightedChance
    {
        int[] Maximum = new int[Int16.MaxValue];
        int[] Minimum = new int[Int16.MaxValue];
        int maxChance;
        Random ran = new Random();

        int check;

        public WeightedChance(int[] ChanceData)
        {
            int lowBound = 0;
            int id = 0;

            foreach (int entry in ChanceData)
            {
                if (entry == 0)
                {
                    continue;
                }

                Minimum[id] = lowBound;
                Maximum[id++] = lowBound + entry - 1;
                lowBound += entry;
            }

            if (lowBound == 0)
            {
                throw new Exception("Error 2: No chance for getting anything.");
            }

            maxChance = id - 1;
        }

        public int Next()
        {
            int hit = ran.Next(0, Maximum[this.maxChance]);
            int id = 0;

            while (true)
            {
                if (id == this.Maximum.Length)
                {
                    throw new Exception("Error 1: Unexpected range value. Report this bug with source code, if possible.");
                }

                if (this.Maximum[id] < hit)
                {
                    id++;
                    continue;
                }
                else
                {
                    return id;
                }
            }
        }

        public int NextBisect()
        {
            int hit = ran.Next(0, Maximum[this.maxChance]);
            int min = 0, max = this.maxChance;

            while (true)
            {
                check = (min + max) / 2;

                if (min > max)
                {
                    check = min;
                }

                if (Minimum[check] <= hit && Maximum[check] >= hit)
                {
                    return check;
                }
                else
                {
                    if (Minimum[check] > hit)
                    {
                        max = check - 1;
                    }
                    else
                    {
                        min = check + 1;
                    }
                }
            }
        }
    }
}


Code: frmMain.Designer.cs

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
namespace WeightedChance
{
    partial class frmMain
    {
        /// <summary>
        /// Erforderliche Designervariable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Verwendete Ressourcen bereinigen.
        /// </summary>
        /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Vom Windows Form-Designer generierter Code

        /// <summary>
        /// Erforderliche Methode für die Designerunterstützung.
        /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
        /// </summary>
        private void InitializeComponent()
        {
            this.txtResult = new System.Windows.Forms.Label();
            this.btnCheck = new System.Windows.Forms.Button();
            this.btnCheckBisect = new System.Windows.Forms.Button();
            this.txtColors = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // txtResult
            // 
            this.txtResult.AutoSize = true;
            this.txtResult.Location = new System.Drawing.Point(1238);
            this.txtResult.Name = "txtResult";
            this.txtResult.Size = new System.Drawing.Size(1613);
            this.txtResult.TabIndex = 0;
            this.txtResult.Text = "...";
            // 
            // btnCheck
            // 
            this.btnCheck.Location = new System.Drawing.Point(19512);
            this.btnCheck.Name = "btnCheck";
            this.btnCheck.Size = new System.Drawing.Size(7523);
            this.btnCheck.TabIndex = 1;
            this.btnCheck.Text = "Scan";
            this.btnCheck.UseVisualStyleBackColor = true;
            this.btnCheck.Click += new System.EventHandler(this.btnCheck_Click);
            // 
            // btnCheckBisect
            // 
            this.btnCheckBisect.Location = new System.Drawing.Point(27612);
            this.btnCheckBisect.Name = "btnCheckBisect";
            this.btnCheckBisect.Size = new System.Drawing.Size(7523);
            this.btnCheckBisect.TabIndex = 2;
            this.btnCheckBisect.Text = "Bisect";
            this.btnCheckBisect.UseVisualStyleBackColor = true;
            this.btnCheckBisect.Click += new System.EventHandler(this.btnCheckBisect_Click);
            // 
            // txtColors
            // 
            this.txtColors.Location = new System.Drawing.Point(11414);
            this.txtColors.Name = "txtColors";
            this.txtColors.Size = new System.Drawing.Size(7520);
            this.txtColors.TabIndex = 3;
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(1217);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(9613);
            this.label1.TabIndex = 4;
            this.label1.Text = "Anzahl Farben (=k)";
            // 
            // frmMain
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(540200);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.txtColors);
            this.Controls.Add(this.btnCheckBisect);
            this.Controls.Add(this.btnCheck);
            this.Controls.Add(this.txtResult);
            this.Name = "frmMain";
            this.Text = "VampireSilence WeightedChance (Benchmark)";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Label txtResult;
        private System.Windows.Forms.Button btnCheck;
        private System.Windows.Forms.Button btnCheckBisect;
        private System.Windows.Forms.TextBox txtColors;
        private System.Windows.Forms.Label label1;
    }
}


Einziger Wermutstropfen ist vllt, dass die Rohdaten aufsteigend geordnet sein müssen, aber das lässt sich ja relativ schnell realisieren. Eine Binary spare ich mir jetzt mal, die könnt ihr ja jetzt selbst kompilieren und ggf. auch direkt die Methoden für die beiden Benchmarks mit einfügen.

Ich weiss nur leider nicht genau, wie ich die Ergebnisse jetzt in CPU-Takte etc. umrechne bzw. wie ich diese ermittle. Wäre nett, wenn das noch einer von euch tun könnte, um dieses kleine Experiment zu vervollständigen. :)

Danke nochmal für die Hilfe und die Anregung !

mfg
- VampireSilence


Horst_H - Mi 01.12.10 18:28

Hallo,

ich erkenne Deine Binärsuche in Zeile 131..154 nicht, das ist eine lineare Suche.
Die Binäresuche ist das Bisektionsverfahren.

Die Cpu Take bestimmst Du einfach aus der Laufzeit/n * CPU-Frequenz
200, ~270k, ~1700k
500, ~113k, ~1600k

Also 1700k 1/s -> Laufzeit = 1/(1700e3 1/s)= 578 ns * 1,66 Ghz = 959 CPU-Takte
//Bei mir Farben 2Mio ( 19 mal Bisec )Laufzeit 6,2s/ 10 Mio = 620 ns * 2.9 Ghz = 1800 CPU-Takte
//Bei mir Farben 255 ( 7 mal Bisec )Laufzeit 6.976s/ 100 Mio = 69,7 ns * 2.9 Ghz = 202 CPU-Takte
// also dauert einmal Bisec etwa 133 Takte bei Dir ~ 80..90 CPU-Takte

Das Gewicht ist nicht n^2, sondern (2*n-1) = (n^2 -(n-1)^2 ), also bezogen auf den Vorgänger.
Der relative Anteil (2*n-1)/ Summe( i= 1..n) i^2 )

int.Maxvalue müßte doch 2.1e9 sein dann ist Wurzel daraus 46340.

Das Verfahren huuuuuuh ist eindeutig schneller bei ganzzahligen Gewichten, die nicht ins Unendliche ( Summe aller Gewichte ) streben.

Gruß Horst


VampireSilence - Mi 01.12.10 20:27

Achso, ich dachte die Binärsuche wäre das lineare Verfahren. Aber das Gewicht ist definitiv n^2, wie du in Zeile 32 und 65 nachlesen kannst.

Ein paar Fragen noch dazu:
Wie nennt man denn dann die lineare Suche ?
Was meinst du mit relativer Anteil ?
Und was berechnest du mit der Wurzel der int.MaxValue ?

mfg
- VampireSilence


Horst_H - Mi 01.12.10 21:43

Hallo,
Zitat:
Wie nennt man denn dann die lineare Suche ?

lineare Suche nennt man lineare Suche.
binäre Suche ist dein Bisec genanntes Verfahren.
Einfache Intervallschachtelung.

Du hast recht mit deinen Gewichten wenn entry der Inhalt von ChanceData[id] ist:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
foreach (int entry in ChanceData)
            {
                if (entry == 0)
                {
                    continue;
                }

                Minimum[id] = lowBound;
                Maximum[id++] = lowBound + entry - 1;
                lowBound += entry;
            }

Wenn chances= 1,4,9,16,25... ist, dann ist
Minimum[] = 0,1,(+4)5,(+9)14,...
Maximum[] = 0,(+4)4,(+9)13,(+16)29,..

Zitat:
Was meinst du mit relativer Anteil ?

Summe Quadratzahle = n*(n+1)*(2*n+1)/6
Sei chances= 1,4,9,16,25 dann ist die Summe 5*6*11 /6 = 55
Dann ist der relative Anteil der Treffer auf chances[4] = 25 eben 25/55 = 5/11 also fast die Hälfte aller Treffer.

Zitat:
Und was berechnest du mit der Wurzel der int.MaxValue ?

Du sagst Dein Program steigt bei n= 1860 aus.
Jetzt sehe ich das mit der Maximumbestimmung richtig, und kann es jetzt berechnen ;-)
n*(n+1)*(2*n+1)/6 muss kleiner int.Maxvalue sein.
Grob gerechent (3* int.Maxvalue)^(1/3) = 1860,73 aha? AHA!

Gruß Horst
EDIT: Mit Int64 kommst Du für die Felder Minimum und Maximum weiter bis knapp über 3 Millionen.
Aber Du brauchst nur ein Feld der Beiden, das um einen Feldplatz länger ist. Es beginnt dann mit 0 und entspricht Deinem Minimum-Feld, wo am Ende noch der höchste Wert ( bei mit sumAnteile, bei Dir der letzte Wert von lowbound) eingetragen wird.
Es wird bei der Binärsuche erst auf >= verglichen , wenn das nicht der Fall ist, muss es kleiner sein, also <=(Wert-1) , wenn es dann größer als der Wert darunter ist ( bei Dir Minimum ) ist es passend.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
procedure IndexWahrschFeldZiehungen;
var 
  i,o,u , m: longint;
  p : longint;
begin
  ZaehlfeldLoeschen;
  For i := 1 to cZiehungen do
    begin
    p := random(sumAnteile);
    //binaere Suche
    o := cAnzahlNamen;
    u := 0;
    repeat
      m := (o+u) div 2;
      if p >= IndexWahrschFeld[m] then
        u := m+1
      else 
        begin
        if  (p >= IndexWahrschFeld[m-1])  then
           break    
        else
          o := m-1;  
        end;  
    until o< u;
    
    inc(ZaehlFeld[m-1]);    

    end;
end;


VampireSilence - Do 02.12.10 13:13

Nachtrag. Ich habe die binäre Suche (Nochmal Sorry, ich habs nicht so mit den Begrifflichkeiten) jetzt so umgeschrieben, dass sie nur noch die Maximum[]-Werte benötigt. Aufgrund einer Ausnahme in Zeile 168, für den Fall check=0, musste ich jetzt allerdings eine Ausnahmecondition schreiben, die alles in allem so aussieht:


C#-Quelltext
1:
if ((check == 0 || Maximum[check - 1] < hit) && Maximum[check] >= hit)                    


Unter der Annahme, dass (Maximum[check - 1] < hit) genau so teuer ist, wie (Minimum[check] <= hit), kostet mich die zusätzliche Condition (check == 0) allerdings fast 40% der Geschwindigkeit. Der einzige Vorteil ist, dass ich mir die Definition (und damit auch den Speicher) für Minimum[] spare. Wie so oft ist es nun also die Entscheidung zwischen Speicher und Performance. Und da muss ich sagen, dass ich einfach mehr Wert auf die Peformance lege, als auf den Speicher, zumal der Speicherunterschied nahezu marginal ist.

mfg
- VampireSilence


Horst_H - Do 02.12.10 13:30

Hallo,

ich habe darüber auch geschrieben, dass ich das Feld eine Position länger gemacht habe.
Deshalb ist bei mir der check nicht notwendig.

Gruß Horst


VampireSilence - Do 02.12.10 13:59

Ok, das is nen guter Trick. Jetzt hatte ich allerdings das Problem, dass die Gewichtung nach (Maximum[n] = lowBound + entry - 1) im Falle entry=0 auch Maximum[n] = 0 ergibt. Mein Array beginnt also mit den Werten [0, 0, ..] und das führt zu einer Endlosschleife. Das Problem habe ich jetzt gelöst, indem ich eine einzelne Condition vor die Schleife eingefügt habe, die folgendermaßen aussieht:


C#-Quelltext
1:
2:
3:
4:
            if (hit == 0)
            {
                return min;
            }


min habe ich dabei außerdem auf 1 korrigiert. Trotzdem habe ich jetzt allerdings eine Geschwindigkeitseinbuße von etwas mehr als 50% und das kann ich mir nicht erklären. Die Einzige Möglichkeit wäre die Operation (check - 1), aber das kann doch nicht so teuer sein, oder doch ?

mfg
- VampireSilence