Viele Programme brauchen (scheinbar) zufällige Werte, um Ereignisse aus der realen Welt zu simulieren.
Häufige und bekannte Beispiele sind:
('H', 'T').Wirklich zufällige Werte mit einem Computer zu erzeugen, ist eine überraschend schwierige technische Herausforderung. Deshalb werden diese Ergebnisse manchmal als „pseudozufällig“ bezeichnet.
Wichtig: Dieses Konzept behandelt nicht kryptografisch sichere Zufallszahlen, die eine noch viel schwierigere Herausforderung sind.
Gut entworfene Bibliotheken wie das Modul Random in der Julia-Standardbibliothek sind jedoch schnell, flexibel und liefern Ergebnisse, die für die meisten Anwendungen in Modellierung, Simulation und Spielen mehr als gut genug sind.
Julia verteilt die Zufallsfunktionalität auf mehrere Orte:
Base, die immer verfügbar sind.Random.Random ist Teil der Standardbibliothek und wahrscheinlich schon vorinstalliert. Du musst aber using Random am Anfang deines Programms einfügen, um die Inhalte in den Namensraum zu holen.
rand()
Was diese Funktion macht, hängt von den Argumenten ab, die du ihr gibst. Es gibt viele Möglichkeiten.
Ohne Argumente erzeugt sie eine Gleitkommazahl zwischen 0 (einschließlich) und 1.
Das ist eine uniform-Verteilung, bei der alle Werte gleich wahrscheinlich sind, wie weiter unten im Abschnitt „Arbeiten mit Verteilungen“ beschrieben.
Ein einzelnes Ganzzahlargument erzeugt einen Vektor dieser Länge.
julia> rand()
0.10261774967264703
julia> rand(5)
5-element Vector{Float64}:
0.24134501977563894
0.5664193284851202
0.9804412082089355
0.6229551330613335
0.47589221741904664
Für einen anderen Bereich verschiebst und skalierst du das Ergebnis einfach passend.
Das folgende Beispiel nutzt Broadcasting für die Subtraktion, das im Konzept „Vektoroperationen“ behandelt wird.
Das .- wendet diese Arithmetik einfach auf jedes Vektorelement an.
# numbers between -1.0 and +1.0
julia> (rand(5) .- 0.5) * 2
5-element Vector{Float64}:
-0.5303906759076336
0.9635682226775855
-0.048823697086981754
0.465842804648374
0.9880834344780736
Wenn ein Typ das einzige Argument ist, verwendet rand typemin und typemax als Grenzen.
Das ist wahrscheinlich nicht das, was du willst!
Für zufällige Ganzzahlen können wir einen Bereich angeben, und optional, wie viele Werte erzeugt werden sollen.
julia> rand(Int64)
-9159538335234594326 # not very useful
julia> rand(1:10, 5)
5-element Vector{Int64}:
1
1
1
4
7
Im obigen Beispiel rand(1:10, 5) fällt auf, dass Werte (zufällig) mehrfach vorkommen, weil jede Auswahl unabhängig ist.
Das ist „Ziehen mit Zurücklegen“, worauf wir weiter unten genauer eingehen.
Für Gleitkommawerte in einem Bereich musst du normalerweise eine Schrittweite angeben. Sonst ist die Schrittweite standardmäßig 1.0, was selten nützlich ist.
julia> rand(2.4:0.01:3.2, 4)
4-element Vector{Float64}:
3.19
2.53
3.14
3.13
Alternativ gibst du ein Array oder ein Tupel an, und rand gibt einen zufälligen Eintrag zurück:
julia> rand([4, 9, 16, 25])
16
# coin flip
julia> rand(['H', 'T'])
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
# mixed types in tuple
julia> rand( (1, 3.2, "name"), 2 )
2-element Vector{Any}:
1
"name"
Stell dir vor, wir haben einen Beutel mit 3 roten und 4 grünen Kugeln und ziehen zufällig eine Kugel heraus. Für eine zweite Kugel gibt es zwei Möglichkeiten:
Szenario 1 ist mit Zurücklegen, Szenario 2 ohne, und sie liefern unterschiedliche Ergebnisse.
Um das Ziehen ohne Zurücklegen in Julia zu simulieren, gibt es ein paar Möglichkeiten.
Am einfachsten (und innerhalb von Exercism die einzige Möglichkeit) verwendest du Random.shuffle(), um die Einträge in eine zufällige Reihenfolge zu bringen, und nimmst dann die ersten n Elemente.
Das ist für kleine Probleme in Ordnung, skaliert aber bei großen Sammlungen möglicherweise schlecht: shuffle muss das gesamte Array erzeugen, auch wenn du nur einen kleinen Teil davon willst.
Um das Ziehen mit Zurücklegen „richtig“ zu machen, installierst du das Paket StatsBase.jl.
Das bietet die Funktion sample() mit einer breiten Palette an Optionen.
Wir können begründet hoffen, dass ähnliche Funktionalität in einer künftigen Version in Random aufgenommen wird, um sie zu einem Teil der Standardbibliothek zu machen (die Codebeispiele in diesem Dokument wurden mit Julia 1.11 getestet).
Bisher haben wir uns auf Fälle konzentriert, in denen alle Ergebnisse gleich wahrscheinlich sind.
Zum Beispiel gibt rand(1:100) mit gleicher Wahrscheinlichkeit jede Ganzzahl von 1 bis 100 zurück.
Viele Situationen aus der realen Welt sind weit weniger einfach.
Deshalb haben Statistiker eine Vielzahl von distributions entwickelt, um „reale“ Ergebnisse mathematisch zu beschreiben.
Die oben beschriebene Funktion rand() wird verwendet, wenn alle Wahrscheinlichkeiten gleich sind.
Das nennt man eine uniform-Verteilung.
Sie wird auch „Normalverteilung“ oder „Glockenkurve“ genannt und ist eine sehr verbreitete Art, Ungenauigkeiten bei gemessenen Werten zu beschreiben.
Angenommen, die Fabrik, in der du arbeitest, hat gerade 10.000 Schrauben gekauft, die eigentlich identisch sein sollten.
Du willst den Fabrikroboter so einrichten, dass er sie handhabt, wiegst also eine Stichprobe von 100 Stück und stellst fest, dass sie ein durchschnittliches (oder mean) Gewicht von 4,731g haben.
Es ist extrem unwahrscheinlich, dass das bedeutet, dass sie alle genau 4,731g wiegen.
Vielleicht stellst du fest, dass die Werte von 4,627 bis 4,794g reichen, sich aber um 4,731g häufen.
Das ist die Gaussian distribution, bei der die Wahrscheinlichkeiten beim Mittelwert am höchsten sind und zu beiden Seiten symmetrisch abfallen (daher „glockenförmig“).
Um das in Software zu simulieren, brauchen wir eine Möglichkeit, die Breite der Kurve anzugeben (typischerweise häufen sich teure Schrauben enger um den Mittelwert als billige!).
Üblicherweise geschieht das mit der standard deviation: kleine Werte für eine scharfe, schmale Kurve, große für eine flache, breite Kurve.
Mathematiker lieben griechische Buchstaben, also verwenden wir μ („mu“), um den Mittelwert darzustellen, und σ („sigma“) für die Standardabweichung.
Wenn du also liest, dass „95 % der Werte innerhalb von 2σ um μ liegen“ oder „das Higgs-Boson wurde mit 5-Sigma-Konfidenz nachgewiesen“, beziehen sich solche Aussagen auf die Standardabweichung.
Mehr dazu gibt es im Konzept Statistics.
randn()
Die Abkürzung steht für „random normal“. Sie ähnelt der Gleitkomma-Variante von rand(), nur dass die Werte gaußverteilt mit Mittelwert 0 und Standardabweichung 1 sind.
Auch hier willst du die Rohausgabe von randn vielleicht für die Standardabweichung skalieren und für den Mittelwert verschieben.
Das folgende Beispiel wandelt sie in einen Mittelwert von 30 und eine Standardabweichung von 5 um.
julia> raw = randn(5)
5-element Vector{Float64}:
3.0762588867281475
1.5101100620253902
-0.5914858221637778
0.684175554069735
-0.8416433926114673
julia> raw * 5 .+ 30
5-element Vector{Float64}:
45.38129443364074
37.55055031012695
27.04257088918111
33.420877770348675
25.791783036942665
An der Ausgabe ist schwer zu erkennen, dass die Rohwerte näher bei null liegen als bei einer Gleichverteilung. Wenn du es bezweifelst, erzeuge 1000 oder mehr und zeichne sie auf, damit es deutlicher wird.
Random
Dieses Modul enthält die nächste Stufe der Funktionalität, die aus Base weggelassen wurde, um die Größe von Julias Standardkonfiguration gering zu halten.
Random ergänzt rand und randn in Base um mutierende Varianten, rand! und randn!.
Eine nützliche Ergänzung ist randstring, das einen String mit gegebener Länge erzeugt.
Standardmäßig verwendet es Groß- und Kleinbuchstaben sowie die Ziffern 0 bis 9, aber auch andere Zeichen können angegeben werden.
julia> using Random
julia> randstring(20)
"BoJnIxrS33pJiWggXZQV"
Außerdem gibt es die Funktion bitrand, die ein zufälliges BitArray mit der angegebenen Länge erzeugt.
julia> bitrand(5)
julia> bitrand(5)
5-element BitVector:
1
1
0
0
1
Um Einträge in einem Vector zufällig zu mischen, gibt es shuffle; außerdem shuffle!, um den Eingabevektor direkt an Ort und Stelle zu verändern.
julia> v = ['A', '1', '2', 'J', 'Q', 'K'];
julia> shuffle(v)
6-element Vector{Char}:
'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
'1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
'2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)
# shuffles are random:
julia> shuffle(v)
6-element Vector{Char}:
'2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)
'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
'1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)
Manchmal ist es nützlich, stattdessen die gemischten Indizes zu haben.
Dafür verwendest du randperm(n), wobei n die Länge der Sequenz ist.
julia> randperm(6)
6-element Vector{Int64}:
6
2
4
1
3
5
Tatsächlich liefert das obige Beispiel dieselben Ergebnisse wie shuffle(1:6).
Mary ist ein großer Fan der Fernsehserie Star Trek: The Next Generation. Sie spielt oft Pen-and-Paper-Rollenspiele, in denen sie und ihre Freunde in die Rolle der Crew des Raumschiffs Enterprise schlüpfen. Marys Charakter ist Captain Picard, was bedeutet, dass sie das Logbuch des Captains führen muss. Sie liebt den kreativen Teil des Spiels, mag es aber nicht, Zufallsdaten spontan zu erzeugen.
Hilf Mary, indem du Zufallsgeneratoren für Daten erstellst, die häufig im Logbuch des Captains vorkommen.
Das Raumschiff Enterprise trifft auf seinen Reisen auf viele Planeten. Planeten im Star-Trek-Universum werden anhand ihrer Eigenschaften in Kategorien eingeteilt. Zum Beispiel ist die Erde ein Planet der Klasse M. Alle möglichen Planetenklassen sind: D, H, J, K, L, M, N, R, T und Y.
Implementiere die Funktion random_planet. Sie soll eine der Planetenklassen zufällig zurückgeben.
julia> random_planet_class()
'K'
Die Enterprise (Registernummer NCC-1701) ist nicht das einzige Raumschiff, das herumfliegt! Wenn sie sich mit einem anderen Raumschiff trifft, muss Mary die Registernummer dieses Raumschiffs ins Logbuch eintragen.
Registernummern beginnen mit dem Präfix „NCC-“ und verwenden dann eine Zahl von 1000 bis 9999 (einschließlich).
Implementiere die Funktion random_ship_registry_number, die eine zufällige Raumschiffregisternummer zurückgibt.
julia> random_ship_registry_number()
"NCC-1947"
Was nützt ein Logbuch, wenn es keine Datumsangaben enthält?
Eine Sternzeit ist eine Gleitkommazahl. Die Abenteuer des Raumschiffs Enterprise aus der ersten Staffel von The Next Generation spielen zwischen den Sternzeiten 41000.0 und 42000.0. Die „4“ steht für das 24. Jahrhundert, die „1“ für die erste Staffel.
Implementiere die Funktion random_stardate, die eine Gleitkommazahl zwischen 41000.0 (einschließlich) und 42000.0 zurückgibt.
julia> random_stardate()
41458.15721310934
Es gab Beschwerden über zu viele Dezimalstellen in Sternzeiten, also beschließt Mary, die Logbucheinträge auf eine Dezimalstelle zu runden.
Obwohl man dies durch Runden der Werte von random_stardate() erreichen könnte, stellst du fest, dass es einen viel einfacheren Ansatz gibt.
Implementiere die Funktion random_stardate_v2, die eine Gleitkommazahl zwischen 41000.0 und 42000.0 (einschließlich) mit einer einzigen Dezimalstelle zurückgibt.
julia> random_stardate_v2()
41732.6
Eine Version des Spiels beinhaltet die Interaktion mit anderen Raumschiffen, und Mary braucht eine Auswahl.
Es gibt eine vollständige Liste möglicher Raumschiffe, aber in jedem Spiel braucht man nur wenige davon, und sie sollten zufällig ausgewählt werden.
Implementiere die Funktion pick_starships(starships, number_needed), die einen Vektor eindeutiger Raumschiffregisternummern entgegennimmt und einen Vektor der Länge number_needed zurückgibt.
Die Ergebniswerte sollten aus starships stammen und keine Duplikate enthalten.
julia> pick_starships(["NCC-5011", "NCC-1228", "NCC-7039", "NCC-3978", "NCC-1476"], 2)
2-element Vector{String}:
"NCC-1228"
"NCC-5011"
Wir haben das Argument starships auf einen Vektor beschränkt.
Wie würdest du pick_starships() implementieren, damit es mit anderen Iterables wie Tupeln oder Mengen funktioniert?
Für diesen Teil gibt es keine Tests.
Melde dich bei Exercism an, um Julia mit 35 Konzepte128 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.