Glossar: Erzeugung von Zufallswerten | schließen |
Der Verzicht auf Zufallszahlen in der Programmierung ist nicht hinnehmbar; sowohl die Simulation naturwissenschaftlicher Modelle als auch Computerspiele - um nur zwei Beispiele zu nennen - benötigen zwingend zufällige Werte. Die Informatik reagiert mit ihrer Standardmethode, wenn das gewünschte nicht erreichbar ist, indem sie sich statt des gewünschten einer anderen Sache bedient, die für den vorliegenden Anwendungsfall hinreichend Ähnlichkeiten mit dem gewünschten aufweist. Das bedeutet hier: Statt echter Zufallszahlen werden zwar nicht zufällige, aber immerhin unvorhersehbare Werte verwendet (in der Regel als Pseudozufallszahlen bezeichnet).
Es gibt zwei gängige Methoden, Pseudozufallszahlen zu erzeugen:
- Rückgriff auf eine Liste echter Zufallszahlen
- Kopplung der Werte an ein zwar nicht zufälliges, aber unabhängiges Ereignis (oft die Uhrzeit)
Die erste Methode ist nicht so unsinnig, wie es auf den ersten Blick erscheint. Ihr Vorteil besteht darin, zufällig wirkende Ergebnisse2 liefern zu können; ihr Nachteil besteht in der Deckungsgleichheit der Werte bei wiederholter Anwendung.
Die zweite Methode ist effektiv, aber nicht immer unbedenklich. Wollen
Sie beispielsweise auf Knopfdruck des Nutzers hin eine Zahl zwischen
0 und 999 erhalten, könnten Sie
einfach diese Zahl mit dem Millisekundenwert der aktuellen Uhrzeit
belegen. Dieser Wert ist zwar alles andere als zufällig, aber unter
der Annahme, daß der Nutzer den Zeitpunkt des Knopfdrucks nicht bewußt
wählt (jedenfalls nicht mit einer Präzision im Millisekundenbereich),
wird im vorliegenden Anwendungsfall der Millisekundenwert die gleichen
Eigenschaften wie eine zufällig ausgewählte Zahl haben.
Die Schwierigkeit besteht in der Synchronisation der Wertebereiche.
Hätten Sie im vorangegangenen Beispiel eine Zahl zwischen 0
und 666 erhalten wollen, wäre ein naheliegender Vorschlag,
den Zufallswert als Millisekunden % 667 (allgemeiner
gesprochen: Millisekunden geteilt durch Höchstwert, davon der Rest)
zu berechnen. Das genügt der Aufgabenstellung insofern, daß der ermittelte
Wert a) unvorhersehbar ist, b) innerhalb des Wertebereichs liegt und
c) alle Werte des Wertebereichs möglich sind. Dennoch ist die Vorgehensweise
suboptimal, da statistisch gesehen die untere Hälfte des Wertebereichs
doppelt so häufig3
ausgewählt werden wird wie die obere.
Meist werden beide Möglichkeiten miteinander verbunden: Eine Zeitangabe regelt Einstiegspunkt und Art, wie die Liste der Zufallszahlen durchschritten wird. Moderne Skriptsprachen erledigen das automatisch für Sie, da ihre Erfinder die (durchaus plausible) Ansicht vertreten, Zufallswerte sollten grundsätzlich abwechslungsreich sein. Bei maschinennahen Sprachen müssen Sie das selbst erledigen - haben dafür aber die Möglichkeit, den Einstiegspunkt den individuellen Bedürfnissen anzupassen.
→ Initialisierung von Zufallswerten in C
In C beispielsweise werden durch
[int] rand(void);
aus der Bibliothek <stdlib.h> Pseudozufallszahlen erzeugt;
ohne weitere Angaben allerdings immer die gleichen. Den Einstiegspunkt
in die Liste kann man durch
[void] srand(unsigned int);,
ebenfalls aus <stdlib.h> festlegen, die Zeitangabe
erhalten Sie durch
[time_t] time(time_t *);
aus <time.h>.
Wenn Sie einen reinen C - Compiler Ihr eigen nennen, sollte
srand(time(NULL)); zu Beginn
der main - Funktion genügen; falls Ihr Compiler sich eher für C++
zuständig fühlt und dementsprechend pingelig bezogen auf Typsicherheit
ist, sollten Sie ihn nicht verärgern und etwas wie das folgende schreiben:
|
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(int argc, char** argv) { int i; int limit = 10; time_t t; time(&t); srand((unsigned int) t); printf( "\nRandom numbers between 0 and %d:\n", limit ); for ( i = 0; i < limit; i++ ) { //time(&t); //srand((unsigned int) t); printf( "%d \t", (rand() % limit) ); } return (EXIT_SUCCESS); } |
Ein häufig begangener Fehler besteht darin, sozusagen zur Sicherheit vor jeder Generierung einer Pseudozufallszahl die Initialisierung erneut aufzurufen. De facto bewirkt das das Gegenteil des gewünschten: Liegen die Zeitpunkte der Generierung weit genug auseinander, verschenken Sie lediglich die Vorteile der Listennutzung; liegen sie dicht beieinander wie in obigem Beispiel, erhalten Sie nur eine einzige Pseudozufallszahl (und die immer wieder). Die beiden orangefarbenen Zeilen dürfen also auf keinen Fall auskommentiert werden.
→ Zufällige Reihenfolge feststehender WerteManchmal soll eine Sammlung mit Werten von x bis y in zufälliger Reihenfolge befüllt werden, wobei jeder der Werte genau einmal vorkommen soll. Man fasse das als Variation der Aufgabe Schüttle eine Sammlung gründlich durch auf (eventuell verbunden mit der Erstellung der Ursprungsmenge). Als naheliegender Ansatz erscheint, jeweils eine Pseudozufallszahl zu ermitteln, sie, falls sie noch nicht vorhanden ist, hinzuzufügen, falls doch, eine neue zu ermitteln. Diese Herangehensweise ist unbedingt zu vermeiden. Folgender Codeschnipsel, um die Konzentration nicht durch formelle Dinge abzulenken in Javascript geschrieben, soll das demonstrieren:
|
/**
* AVOID THIS
*/
function contains_element (the_array, the_element) {
for ( var i = 0; i < the_array.length; i++ ) {
element_containing_proove++;
if ( the_array[i] == the_element )
return 1;
}
return 0;
}
var collection = [];
var LIMIT = 100;
var i, random_value;
var element_containing_proove = 0;
var used_random_values = 0;
for ( i = 0; i < LIMIT; i++ ) {
do {
used_random_values++;
random_value = Math.floor(Math.random() * LIMIT);
}
while ( contains_element(collection, random_value) );
collection[collection.length] = random_value;
}
console.log( "element_containing_proove: " + element_containing_proove );
console.log( "used_random_values: " + used_random_values );
|
Je voller die Zielsammlung wird, desto mehr nutzlose Pseudozufallszahlen müssen erzeugt und wieder verworfen werden. Das wirkt sich umso stärker aus, wenn die angestrebte Zielsammlung groß ist.
Es gibt zwei sinnvolle Methoden, diese Aufgabe zu lösen; welche zu
bevorzugen ist, hängt davon ab, ob die Sammlung eher Eigenschaften
einer Liste oder eines Feldes besitzt.
Eine Liste ist eine Sammlung von Elementen, bei denen jedes
außer seinem eigentlichen Wert noch einen Verweis auf das
folgende Element4
hat. Listen sind unpraktisch, wenn man ein Element mit einer bestimmten
Ordnungszahl herausgreifen will, da man immer die ganze Liste abschreiten
muß; dagegen ist es sehr einfach, die Verknüpfungen der Liste zu ändern
(wodurch man beispielsweise ein einzelnes Element löschen kann oder
einen Teil der Liste abkoppeln und wie beim Mischen von Spielkarten
hinten anhängen kann).
Ein Feld (mittlerweile auch im deutschsprachigen Raum wohl
besser unter dem Namen Array bekannt) ist eine Sammlung von
Elementen, die jeweils
unter einer Ordnungszahl5
abgespeichert sind und über diese direkt angesprochen werden können.
Der Preis dafür sind aufwendige Operationen, um die Elemente innerhalb
der Liste zu verschieben.
Das Durchschütteln einer Liste erfolgt nach dem Muster
Das Durchschütteln eines Feldes erfolgt nach dem Muster
In Javascript haben Arrays sowohl Feld- als auch Listeneigenschaften, deshalb ist Javascript sehr gut geeignet zur Gegenüberstellung beider Methoden:
var source = []; var target = []; var LIMIT = 10; var i, random_index; for ( i = 0; i < LIMIT; i++ ) source.push(i); while ( source.length ) { random_index = Math.floor(Math.random() * source.length); target.push(source[random_index]); source.splice(random_index, 1); } console.log( target ); |
var source = []; var LIMIT = 10; var i, random_index, tmp; for ( i = 0; i < LIMIT; i++ ) source[source.length] = i; for ( i = 0; i < LIMIT; i++ ) { random_index = Math.floor(Math.random() * LIMIT); if ( i == random_index ) continue; tmp = source[i]; source[i] = source[random_index]; source[random_index] = tmp; } console.log( source ); |
1
Ob in der Moralphilosophie und Ethik das Äußern der Unwahrheit stets
als lügen anzusehen ist, ist nicht Gegenstand dieser Betrachtung.
Wenn das Äußern der Unwahrheit seitens einer Maschine als lügen
gilt, ist die Behauptung, Roboter könnten nicht lügen, falsch. Wenn
das Äußern der Unwahrheit seitens einer Maschine nicht als lügen
anzusehen ist, ist die Behauptung, Roboter könnten nicht lügen, trivial
und damit belanglos, da das Unterscheidungskriterium zwischen lügen
und nicht lügen nicht gegeben ist.
Interessante Betrachtungen zum Thema Können Roboter lügen?
finden sich im
gleichnamigen Artikel
von Raúl Rojas; dort liegt allerdings das Augenmerk auf dem Erfolg
einer durch Lügen beabsichtigten Täuschung.
↑
2 Damit soll vermieden werden, daß die Ergebnisse 'zufällig' einem der seltenen Glücks- oder Unglücksfälle entsprechen. Wenn Sie beispielsweise einen zufälligen Wurf mit drei Würfeln demonstrieren wollen, werden drei identische Zahlen nicht Ihr angestrebtes Ziel sein; in der Realität kommt das aber durchschnittlich bei jedem sechsunddreißigsten mal vor. Methoden, 'zufälligere' Werte als bei echtem Zufall zu erzeugen, werden im weiteren Text noch eine Rolle spielen. ↑
3 Wenn die Uhrzeit einen Wert aus den unteren zwei Dritteln annimmt, wird sie unverändert zum Ergebnis. Das letzte Drittel, das außerhalb des gewünschten Wertebereichs ist, fängt jedoch wieder von vorn zu zählen an. Derartige unerwünschte Effekte fallen in der Regel erst bei einer großen Anzahl an Wiederholungen auf, nämlich dann, wenn man sich sicher sein kann, daß trotz aller Zufälligkeit die Gesetze der Wahrscheinlichkeitsrechnung in den Ergebnissen erkennbar sein müßten. ↑
4 Oder auf das vorangehende, oder auf beide... die Elemente, aus denen B-Bäume und T-Bäume gebildet werden, sogar auf noch mehr. ↑
5 Sogenannte assoziative Arrays, eine der Zaubermöglichkeiten von PHP, bei denen die einzelnen Elemente statt mit einer Ordnungszahl über eine Zeichenkette angesprochen werden können, sind ein Sonderfall. Für den hier ausgeführten Gedanken bleibt aber der Aspekt der direkten Ansprache relevant. ↑