Marks Lösung (die akzeptierte Lösung) ist nahezu perfekt.
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
bearbeitet am 25. März 16 um 23:16 Uhr
Mark Amery 39k21170211
Es gibt jedoch eine Einschränkung, die 1 gültigen Satz von Ergebnissen in jedem Szenario verwirft, in dem RAND_MAX
( RM
) 1 weniger als ein Vielfaches von N
(wobei N
= die Anzahl möglicher gültiger Ergebnisse) ist.
dh wenn die 'Anzahl der verworfenen Werte' ( D
) gleich ist N
, dann sind sie tatsächlich eine gültige Menge ( V)
keine ungültige Menge ( I
).
Was dies verursacht, ist, dass Mark irgendwann den Unterschied zwischen N
und aus den Augen verliert Rand_Max
.
N
ist eine Menge, deren gültige Mitglieder nur aus positiven Ganzzahlen bestehen, da sie eine Anzahl von Antworten enthält, die gültig wären. (zB: Set N
= {1, 2, 3, ... n }
)
Rand_max
Es handelt sich jedoch um eine Menge, die (wie für unsere Zwecke definiert) eine beliebige Anzahl nicht negativer Ganzzahlen enthält.
In seiner allgemeinsten Form wird hier Rand Max
die Menge aller gültigen Ergebnisse definiert, die theoretisch negative Zahlen oder nicht numerische Werte enthalten können.
Daher Rand_Max
ist besser definiert als die Menge der "möglichen Antworten".
Jedoch N
arbeitet gegen die Zählung der Werte innerhalb des Satzes von gültigen Antworten, so auch wie in unserem speziellen Fall definiert ist , Rand_Max
wird ein Wert um eins kleiner als die Gesamtzahl sei es enthält.
Bei Verwendung von Marks Lösung werden Werte verworfen, wenn: X => RM - RM% N.
EG:
Ran Max Value (RM) = 255
Valid Outcome (N) = 4
When X => 252, Discarded values for X are: 252, 253, 254, 255
So, if Random Value Selected (X) = {252, 253, 254, 255}
Number of discarded Values (I) = RM % N + 1 == N
IE:
I = RM % N + 1
I = 255 % 4 + 1
I = 3 + 1
I = 4
X => ( RM - RM % N )
255 => (255 - 255 % 4)
255 => (255 - 3)
255 => (252)
Discard Returns $True
Wie Sie im obigen Beispiel sehen können, würden wir den Wert von X (die Zufallszahl, die wir aus der Anfangsfunktion erhalten) 252, 253, 254 oder 255 verwerfen, obwohl diese vier Werte einen gültigen Satz zurückgegebener Werte enthalten .
IE: Wenn die Anzahl der verworfenen Werte (I) = N (die Anzahl der gültigen Ergebnisse) ist, wird ein gültiger Satz von Rückgabewerten von der ursprünglichen Funktion verworfen.
Wenn wir den Unterschied zwischen den Werten N und RM als D beschreiben, dh:
D = (RM - N)
Wenn dann der Wert von D kleiner wird, steigt der Prozentsatz der nicht benötigten Nachwürfe aufgrund dieser Methode bei jedem natürlichen Multiplikativ. (Wenn RAND_MAX NICHT gleich einer Primzahl ist, ist dies von berechtigter Bedeutung.)
Z.B:
RM=255 , N=2 Then: D = 253, Lost percentage = 0.78125%
RM=255 , N=4 Then: D = 251, Lost percentage = 1.5625%
RM=255 , N=8 Then: D = 247, Lost percentage = 3.125%
RM=255 , N=16 Then: D = 239, Lost percentage = 6.25%
RM=255 , N=32 Then: D = 223, Lost percentage = 12.5%
RM=255 , N=64 Then: D = 191, Lost percentage = 25%
RM=255 , N= 128 Then D = 127, Lost percentage = 50%
Da der Prozentsatz der benötigten Rerolls zunimmt, je näher N an RM kommt, kann dies bei vielen verschiedenen Werten von Bedeutung sein, abhängig von den Einschränkungen des Systems, auf dem der Code ausgeführt wird, und den gesuchten Werten.
Um dies zu negieren, können wir eine einfache Änderung vornehmen, wie hier gezeigt:
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
Dies bietet eine allgemeinere Version der Formel, die die zusätzlichen Besonderheiten der Verwendung des Moduls zur Definition Ihrer Maximalwerte berücksichtigt.
Beispiele für die Verwendung eines kleinen Werts für RAND_MAX, der ein Multiplikativ von N ist.
Mark'original Version:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X >= (RAND_MAX - ( RAND_MAX % n ) )
When X >= 2 the value will be discarded, even though the set is valid.
Verallgemeinerte Version 1:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X > (RAND_MAX - ( ( RAND_MAX % n ) + 1 ) % n )
When X > 3 the value would be discarded, but this is not a vlue in the set RAND_MAX so there will be no discard.
In dem Fall, in dem N die Anzahl der Werte in RAND_MAX sein soll; In diesem Fall können Sie N = RAND_MAX +1 setzen, es sei denn, RAND_MAX = INT_MAX.
In Bezug auf die Schleife können Sie einfach N = 1 verwenden, und jeder Wert von X wird jedoch akzeptiert, und Sie geben eine IF-Anweisung für Ihren endgültigen Multiplikator ein. Aber vielleicht haben Sie Code, der einen gültigen Grund hat, eine 1 zurückzugeben, wenn die Funktion mit n = 1 aufgerufen wird ...
Daher ist es möglicherweise besser, 0 zu verwenden, was normalerweise einen Div 0-Fehler liefert, wenn Sie n = RAND_MAX + 1 haben möchten
Verallgemeinerte Version 2:
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
} else {
x = rand();
}
Beide Lösungen lösen das Problem mit unnötig verworfenen gültigen Ergebnissen, die auftreten, wenn RM + 1 ein Produkt von n ist.
Die zweite Version behandelt auch das Edge-Case-Szenario, wenn Sie n benötigen, um dem insgesamt möglichen Wertesatz in RAND_MAX zu entsprechen.
Der modifizierte Ansatz ist in beiden Fällen derselbe und ermöglicht eine allgemeinere Lösung für die Notwendigkeit, gültige Zufallszahlen bereitzustellen und verworfene Werte zu minimieren.
Wiederholen:
Die grundlegende allgemeine Lösung, die das Beispiel der Marke erweitert:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
Die erweiterte allgemeine Lösung, die ein zusätzliches Szenario von RAND_MAX + 1 = n ermöglicht:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
} else {
x = rand();
}
In einigen Sprachen (insbesondere interpretierten Sprachen) kann die Berechnung der Vergleichsoperation außerhalb der while-Bedingung zu schnelleren Ergebnissen führen, da dies eine einmalige Berechnung ist, unabhängig davon, wie viele Versuche erforderlich sind. YMMV!
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x; // Resulting random number
int y; // One-time calculation of the compare value for x
if n != 0 {
y = RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n)
do {
x = rand();
} while (x > y);
x %= n;
} else {
x = rand();
}
RAND_MAX%n == n - 1
_ _ nachzudenken, ist(RAND_MAX + 1) % n == 0
. Wenn ich Code lese, verstehe ich ihn eher% something == 0
als „gleichmäßig teilbar“ als andere Berechnungsmethoden. Wenn Ihre C ++ - stdlibRAND_MAX
den gleichen Wert wie hatINT_MAX
,(RAND_MAX + 1)
würde dies natürlich nicht funktionieren. Daher bleibt Marks Berechnung die sicherste Implementierung.