Daniel: srand()

Hallo,

mit srand kann man Zufallszahlen erzeugen. Die Funktion wird (ohne Argument) mit dem Zeitwert 1.1.1970 (richtig?) initialisiert. Nun, wenn ich z.B. 20 Zufallszahlen nacheinander erzeugen will, wie kann ich dafür sorgen, daß alle 20 Zahlen auch unterschiedlich sind?
Ich hab's mit einer while-Schleife versucht, um die Abarbeitung des Skriptes zu verlangsamen und damit auch unterschiedliche Zahlen zu bekommen aber geht's nicht eleganter?

Gruß
Daniel

  1. Hallo,

    Hi,

    mit srand kann man Zufallszahlen erzeugen. Die Funktion wird (ohne Argument) mit dem Zeitwert 1.1.1970 (richtig?) initialisiert. Nun, wenn ich z.B. 20 Zufallszahlen nacheinander erzeugen will, wie kann ich dafür sorgen, daß alle 20 Zahlen auch unterschiedlich sind?
    Ich hab's mit einer while-Schleife versucht, um die Abarbeitung des Skriptes zu verlangsamen und damit auch unterschiedliche Zahlen zu bekommen aber geht's nicht eleganter?

    ich hab‚s mal damit probiert (Linux, Perl 5.005):

    ----snip--------
    #!/usr/bin/perl

    aus O‚Reilly "Programmieren mit Perl" (S. 233 und 211)

    srand(time ^ $$ ^ unpack "%32L", 'ps axww gzip');
    print rand()."\n";
    print rand()."\n";
    print rand()."\n";
    print rand()."\n";
    -----snap------

    Das ergibt bei mir lauter unterschiedliche Fließkommazahlen zwischen 0 und 1.

    Gruß
    Daniel

    Gruß Jörg

  2. Hi,

    mit srand kann man Zufallszahlen erzeugen. Die Funktion wird (ohne Argument) mit dem Zeitwert 1.1.1970 (richtig?) initialisiert. Nun, wenn ich z.B. 20 Zufallszahlen nacheinander erzeugen will, wie kann ich dafür sorgen, daß alle 20 Zahlen auch unterschiedlich sind?
    Ich hab's mit einer while-Schleife versucht, um die Abarbeitung des Skriptes zu verlangsamen und damit auch unterschiedliche Zahlen zu bekommen aber geht's nicht eleganter?

    Professor Doktor Perldoc sagt dazu:

    In fact, it's usually not necessary to call srand() at all, because if it is not called explicitly, it is called implicitly at the first use of the rand() operator. However, this was not the case in version of Perl before 5.004, so if your script will run under older Perl versions, it should call srand().
    [...]
    Do not call srand() multiple times in your program unless you know exactly what you're doing and why you're doing it. The point of the function is to ``seed'' the rand() function so that rand() can produce a different sequence each time you run your program. Just do it once at the top of your program, or you won't get random numbers out of rand()!

    Also, mit einem entsprechend neuen Perl kannst Du auf srand() komplett verzichten, mit einem älteren benutze es nur einmal im Script. Ansonsten ist jeder Zufallsgenerator nur so gut wie... öh... Moment, seit wann sind Zufallsgeneratoren gut? :-)

    Wenn Du wirklich zufällige Werte[tm] haben willst, solltest Du Dich mit dem (CPAN-)Modul Math::TrulyRandom beschäftigen (siehe http://www.perl.com/CPAN/).

    Cheatah

  3. ...Nun, wenn ich z.B. 20 Zufallszahlen nacheinander erzeugen will, wie kann ich dafür sorgen, daß alle 20 Zahlen auch unterschiedlich sind?

    Folgende Funktion sollte immer unterschiedleche 'Zufallszahlen' erzeugen:

    sub random {
    $random = int(rand $_[1]-$_[0])+$_[0];
    if($random >= $previous_rand) { $random++; }
    $previous_rand = $random;
    }

    Aufruf:
    random(a,b);
    Dabei werden Werte a<=x<=y erzeugt.

    Am Script-Anfang sollte noch das srand() zur Initialisierung stehen.

    Don

    1. Hi!

      Folgende Funktion sollte immer unterschiedleche 'Zufallszahlen' erzeugen:

      sub random {

      »»  $random = int(rand $_[1]-$_[0])+$_[0];
      »»  if($random >= $previous_rand) { $random++; }
      »»  $previous_rand = $random;

      }

      random(a,b);

      Kannst Du das mal erklaeren? Wieso sollte dies "immer" unterschiedliche Zahlen erzeugen? Und was soll die zweite Zeile der Funktion? Sie erhoeht $random, wenn es groesser oder gleich der vorher erzeugten Zahl ist? Die einzige Wirkung, die ich da sehe ist, dass eventuell die durch b gesetzt obere Grenze ueberschritten wird.

      Calocybe

      1. Folgende Funktion sollte immer unterschiedleche 'Zufallszahlen' erzeugen:

        Die Funktion soll keinesfalls als wirklicher Zufallsgenerator fungieren. Hier wird nur das Problem immer wiederkehrender Zahlen ausgeräumt.

        sub random {
        »»  $random = int(rand $_[1]-$_[0])+$_[0];
        »»  if($random >= $previous_rand) { $random++; }
        »»  $previous_rand = $random;
        }

        random(a,b);

        Kannst Du das mal erklaeren? Wieso sollte dies "immer" unterschiedliche Zahlen erzeugen? Und was soll die zweite Zeile der Funktion? Sie erhoeht $random, wenn es groesser oder gleich der vorher erzeugten Zahl ist? Die einzige Wirkung, die ich da sehe ist, dass eventuell die durch b gesetzt obere Grenze ueberschritten wird.

        Ich hätte die Funktion vielleicht genauer beschreiben sollen:

        Die Perl-Anweisung 'int(rand b)' erzeugt eine ganze Zahl zwischen 0 und b, b selbst kann dabei nicht auftreten (0<=x<b). Obige Funktion random(a,b) gibt eine Zahl a<=x<=b zurück. Die obere Grenze kann also erreicht werden.

        In der ersten Zeile der Funktion
        $random = int(rand $_[1]-$_[0])+$_[0];
        wird eine ganze Zahl erzeugt, die zwischen a ($_[0]) und b-1 ($_[1]-1) liegt: a<=x<b. random() soll jedoch einen Wert a<=x<=b liefern. In diesem Bereich steht also ein Wert mehr zur Verfügung, als für rand() in Zeile 1 bereitgestellt wurde.

        In der Variablen $previous_rand ist der Wert gespeichert, der im letzten Aufruf von random() erzeugt wurde. (Zeile 3: $previous_rand = $random;)

        In Zeile 2 wird die aktuell erzeugte Zufallszahl mit $previous_rand verglichen. Wenn wieder dieselbe Zahl erzeugt wurde, soll der aktuelle Wert um 1 erhöht werden. Damit ist sichergestellt, daß bei zwei aufeinanderfolgenden Aufrufen von random() nicht derselbe Wert erzeugt wird.

        Da wir uns vorher beim Aufruf von rand() einen Wert (b) aufgespart haben, müssen alle Werte größer oder gleich $previous_rand um 1 erhöht werden. Damit werden alle Zahlen x mit a<=x<=b erreicht.

        Fazit: Beim Aufruf von rand() wird ein Wert aus dem gewünschten Bereich ausgespart, da wir beim zweiten Aufruf von random() ja eigentlich keine Zahl x mit a<=x<=b wollen, sonden a<=x<$previous_rand oder $previous_rand<x<=b. Also eine Zahl von möglichen a-b Werten.

        Fehler: Ein kleiner Fehler ist allerdings im Script. Es kann beim ersten Aufruf von random() niemals die kleinste gewünschte Zahl (a) erzeugt werden. Dies könnte noch eingefügt werden.

        Zusatz: random() funktioniert so nur für positive natürliche Zahlen. Bei negativen Werten wird der Zahlenbereich 'verschoben', kann aber noch korrigiert werden. Für reelle Werte ist die Funktion gänzlich ungeeignet!

        1. Hi Don!

          Ok, soweit hab ich das verstanden. Allerdings fallen mir da ein paar Dinge auf.

          Die Perl-Anweisung 'int(rand b)' erzeugt eine ganze Zahl zwischen 0 und b, [...]

          Eine _ganze_ Zahl? Nun, in meiner Perl-Doku steht "Returns a random _fractional_ number greater than or equal to 0 and less than the value of EXPR." Damit koennte das +1 also doch die Grenze von b ueberschreiten, naemlich wenn der Returnwert von rand() b-1<x<b ist.

          Da wir uns vorher beim Aufruf von rand() einen Wert (b) aufgespart haben, müssen alle Werte größer oder gleich $previous_rand um 1 erhöht werden. Damit werden alle Zahlen x mit a<=x<=b erreicht.

          Ausser natuerlich der Bereich previous_rand<=x<pevious_rand+1. Aber wenn nur ganze Zahlen zurueckgeliefert werden wuerden, wuerde sich dieser Bereich auf den einzigen Wert previous_rand selbst beschraenken, was ja von Dir beabsichtigt ist.

          Fehler: Ein kleiner Fehler ist allerdings im Script. Es kann beim ersten Aufruf von random() niemals die kleinste gewünschte Zahl (a) erzeugt werden. Dies könnte noch eingefügt werden.

          Naja, previous_rand muss sowieso initialisiert werden, warum also nicht auf eine Zahl, die groesser ist, als alle b's, die im Script uebergeben werden sollen (falls vorher bekannt)?

          Zusatz: random() funktioniert so nur für positive natürliche Zahlen. Bei negativen Werten wird der Zahlenbereich 'verschoben', kann aber noch korrigiert werden.

          Noe, warum? Man muss nur darauf achten, dass a immer kleiner ist als b. Aber das muss man im positiven Bereich sowieso auch tun. Und dran denken: -10 ist kleiner als -4!

          Für reelle Werte ist die Funktion gänzlich ungeeignet!

          Tja, wie gesagt, rand() liefert eben reelle Werte zurueck.

          Calocybe

          P.S. Uebrigens, Daniel's Problem war eigentlich, alle 20 Zahlen unterschiedlich zu halten.

          1. Hi,

            Die Perl-Anweisung 'int(rand b)' erzeugt eine ganze Zahl zwischen 0 und b, [...]

            Eine _ganze_ Zahl? Nun, in meiner Perl-Doku steht "Returns a random _fractional_ number greater than or equal to 0 and less than the value of EXPR."

            ähm... das sagt perldoc aber vermutlich nicht für int()... ;-)

            Damit koennte das +1 also doch die Grenze von b ueberschreiten, naemlich wenn der Returnwert von rand() b-1<x<b ist.

            Nein, da die Nachkommastellen abgeschnitten werden, ist tatsächlich ein Wert a<=x<=b gewährleistet. Allerding ist a<x=b relativ selten, so daß wir hier keinen fairen Zufallsgenerator haben. Je größer b-a, desto seltener x=b.

            Leider kann mit dieser Funktion auch nur gewährleistet werden, daß sich ein Zufallswert vom _vorherigen_ unterscheidet, nicht aber von allen, siehe auch:

            P.S. Uebrigens, Daniel's Problem war eigentlich, alle 20 Zahlen unterschiedlich zu halten.

            Genau.
            Eine Lösung hierzu wäre in etwa folgendes:

            for (my $i=0; $i<$anzahl_werte; $i++) {
               my $temp = int(rand($max_rand)) while ($hash{$temp});
               $hash{$temp} = 1;
               $array[$i] = $temp;
            }

            Anschließend hat man $anzahl_werte paarweise verschiedene Zufallszahlen in @array. Problem: Je höher die Anzahl der Werte und je enger der Zahlenbereich, desto länger (exponential) dauert es.

            Cheatah

            1. Hallo!

              P.S. Uebrigens, Daniel's Problem war eigentlich, alle 20 Zahlen unterschiedlich zu halten.

              Genau.
              Eine Lösung hierzu wäre in etwa folgendes:

              for (my $i=0; $i<$anzahl_werte; $i++) {
                 my $temp = int(rand($max_rand)) while ($hash{$temp});
                 $hash{$temp} = 1;
                 $array[$i] = $temp;
              }

              Anschließend hat man $anzahl_werte paarweise verschiedene Zufallszahlen in @array. Problem: Je höher die Anzahl der Werte und je enger der Zahlenbereich, desto länger (exponential) dauert es.

              Eine Variante hierfür wäre z.B. die Möglichen Zahlen in einem Array zu halten, und dort dann per Zufall eine Rauszuangeln. Per nächsten mal ist das Array dann um diese Zahl kleiner ...
              -----------------------------
              my $max=20;
              my $anz_werte=21;

              my @zahlen;
              @zahlen[0..$max]=(0..$max);

              foreach $i (0..$anz_werte-1)
              {
                  my $temp = splice(@zahlen, int(rand($max+1-$i)), 1);
                  push(@zahlen, $temp);
              }

              print join('-', splice(@zahlen, -$anz_werte));
              -------------------------

              Jörk

              1. Eine Variante hierfür wäre z.B. die Möglichen Zahlen in einem Array zu halten, und dort dann per Zufall eine Rauszuangeln. Per nächsten mal ist das Array dann um diese Zahl kleiner ...

                Das halte ich für die einfachste Lösung.

                Wenn es wirklich darum geht, eine Permutation zu bestimmen, dann geht das auch noch anders.
                Offensichtlich gibt es ja 20! Permutationen der Zahlen von 1 bis 20. Was man also tun könnte, wäre eine Zufallszahl zwischen 1 und 20! zu bestimmen und die einzelnen Zahlen durch sukzessive Division bzw. Restklassenbildung abzuspalten.
                Das könnte bezüglich Performance besser sein als 20 Würfelwürfe ...

              2. Hi,

                Eine Variante hierfür wäre z.B. die Möglichen Zahlen in einem Array zu halten, und dort dann per Zufall eine Rauszuangeln. Per nächsten mal ist das Array dann um diese Zahl kleiner ...

                stimmt, wenn die Zahl der möglichen Zufallszahlen verhältnismäßig klein ist, ist dies sicher "billiger". Um allerdings auch ohne Ganzzahlen zu funktionieren (oder nur mit geraden Zahlen oder allgemein mit beliebigen Zahlenmengen), muß die Routine noch angepaßt werden. Nichtsdestotrotz bleibt das Verfahren anwendbar :-)

                Cheatah

            2. Moin!

              ähm... das sagt perldoc aber vermutlich nicht für int()... ;-)

              Oops, das ist ja peinlich! *g* Da hab ich wohl mal wieder nicht richtig gelesen.

              Leider kann mit dieser Funktion auch nur gewährleistet werden, daß sich ein Zufallswert vom _vorherigen_ unterscheidet, nicht aber von allen, siehe auch:

              Naja, ich halte diesen Fall bei einem vernuenftig grossen Zahlenbereich sowieso fuer ziemlich ausgeschlossen. Bei einem sehr kleinen Bereich ist es dann sowieso meist egal. Muesste man natuerlich mal langzeit-testen.

              for (my $i=0; $i<$anzahl_werte; $i++) {
                 my $temp = int(rand($max_rand)) while ($hash{$temp});
                 $hash{$temp} = 1;
                 $array[$i] = $temp;
              }

              Man muss natuerlich darauf achten, dass $max_rand >= $anzahl_werte ist, sonst hat man ne huebsche kleine Endlosschleife.

              Calocybe