Πολλά προγράμματα χρειάζονται (φαινομενικά) τυχαίες τιμές για να προσομοιώσουν γεγονότα του πραγματικού κόσμου.
Συνήθη και οικεία παραδείγματα περιλαμβάνουν:
('H', 'T').Η παραγωγή πραγματικά τυχαίων τιμών με έναν υπολογιστή είναι μια προκλητικά δύσκολη τεχνική πρόκληση, γι' αυτό μπορεί να δεις αυτά τα αποτελέσματα να αναφέρονται ως "ψευδοτυχαία".
Σημαντικό: Αυτή η Έννοια δεν καλύπτει τους κρυπτογραφικά ασφαλείς τυχαίους αριθμούς, οι οποίοι αποτελούν μια πολύ πιο δύσκολη πρόκληση.
Ωστόσο, καλά σχεδιασμένες βιβλιοθήκες όπως η ενότητα Random στην τυπική βιβλιοθήκη της Julia είναι γρήγορες, ευέλικτες και δίνουν αποτελέσματα που είναι υπεραρκετά καλά για τις περισσότερες εφαρμογές στη μοντελοποίηση, την προσομοίωση και τα παιχνίδια.
Η Julia χωρίζει τη λειτουργικότητα των τυχαίων αριθμών σε πολλά σημεία:
Base, οι οποίες είναι πάντα διαθέσιμες.Random.Η Random είναι μέρος της τυπικής βιβλιοθήκης και πιθανότατα είναι προεγκατεστημένη, αλλά θα χρειαστεί να προσθέσεις using Random στην αρχή του προγράμματός σου για να φέρεις τα περιεχόμενά της στον χώρο ονομάτων.
rand()
Το τι κάνει αυτή η συνάρτηση εξαρτάται από τα ορίσματα που της δίνεις. Υπάρχουν πολλές επιλογές.
Χωρίς ορίσματα, παράγει έναν αριθμό κινητής υποδιαστολής μεταξύ του 0 (συμπεριλαμβανομένου) και του 1.
Αυτή είναι μια κατανομή uniform όπου όλες οι τιμές είναι εξίσου πιθανές, όπως εξηγείται στην ενότητα Εργασία με κατανομές, παρακάτω.
Ένα μόνο ακέραιο όρισμα παράγει ένα διάνυσμα αυτού του μήκους.
julia> rand()
0.10261774967264703
julia> rand(5)
5-element Vector{Float64}:
0.24134501977563894
0.5664193284851202
0.9804412082089355
0.6229551330613335
0.47589221741904664
Για ένα διαφορετικό εύρος, απλώς μετατόπισε και κλιμάκωσε ανάλογα το αποτέλεσμα.
Το παρακάτω παράδειγμα χρησιμοποιεί μετάδοση για την αφαίρεση, η οποία καλύπτεται στην Έννοια Vector Operations.
Το .- απλώς εφαρμόζει αυτή την αριθμητική σε κάθε στοιχείο του διανύσματος.
# 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
Με έναν τύπο ως μοναδικό όρισμα, η rand θα χρησιμοποιήσει τα typemin και typemax ως όρια.
Αυτό μάλλον δεν είναι αυτό που θέλεις!
Για τυχαίους ακεραίους, μπορούμε να δώσουμε ένα εύρος και προαιρετικά πόσες τιμές να παραχθούν.
julia> rand(Int64)
-9159538335234594326 # not very useful
julia> rand(1:10, 5)
5-element Vector{Int64}:
1
1
1
4
7
Στο παραπάνω παράδειγμα rand(1:10, 5), πρόσεξε ότι υπάρχουν (τυχαία) επαναλαμβανόμενες τιμές, επειδή κάθε επιλογή είναι ανεξάρτητη.
Αυτή είναι η "δειγματοληψία με επανατοποθέτηση", που εξηγείται αναλυτικότερα παρακάτω.
Για τιμές κινητής υποδιαστολής μέσα σε ένα εύρος, συνήθως θα χρειαστεί να ορίσεις ένα βήμα. Διαφορετικά, το βήμα θα είναι 1.0 από προεπιλογή, κάτι που σπάνια είναι χρήσιμο.
julia> rand(2.4:0.01:3.2, 4)
4-element Vector{Float64}:
3.19
2.53
3.14
3.13
Εναλλακτικά, δώσε έναν πίνακα ή μια πλειάδα και η rand θα επιστρέψει ένα τυχαίο στοιχείο:
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"
Φαντάσου ότι έχουμε ένα σακούλι που περιέχει 3 κόκκινες μπάλες και 4 πράσινες μπάλες και τραβάμε τυχαία μια μπάλα από το σακούλι. Για να πάρουμε και δεύτερη μπάλα, υπάρχουν δύο δυνατότητες:
Το σενάριο 1 είναι με επανατοποθέτηση, το σενάριο 2 είναι χωρίς, και δίνουν διαφορετικά αποτελέσματα.
Για να προσομοιώσεις δειγματοληψία χωρίς επανατοποθέτηση στην Julia, υπάρχουν δύο επιλογές.
Η απλούστερη (και εντός του Exercism η μόνη επιλογή), χρησιμοποίησε τη Random.shuffle() για να βάλεις τα στοιχεία σε τυχαία σειρά και μετά πάρε τα πρώτα n στοιχεία.
Αυτό είναι εντάξει για μικρά προβλήματα αλλά μπορεί να μην κλιμακώνεται καλά σε μεγάλες συλλογές: η shuffle χρειάζεται να δημιουργήσει ολόκληρο τον πίνακα, ακόμα κι αν θέλεις μόνο ένα μικρό κλάσμα του.
Για να κάνεις δειγματοληψία με επανατοποθέτηση "σωστά", εγκατέστησε το πακέτο StatsBase.jl.
Αυτό παρέχει τη συνάρτηση sample() με μια πλήρη γκάμα επιλογών.
Μπορούμε εύλογα να ελπίζουμε ότι παρόμοια λειτουργικότητα θα προστεθεί στη Random σε κάποια μελλοντική έκδοση, ώστε να γίνει μέρος της τυπικής βιβλιοθήκης (τα δείγματα κώδικα σε αυτό το έγγραφο δοκιμάστηκαν με Julia 1.11).
Μέχρι τώρα, έχουμε επικεντρωθεί σε περιπτώσεις όπου όλα τα ενδεχόμενα είναι εξίσου πιθανά.
Για παράδειγμα, η rand(1:100) έχει την ίδια πιθανότητα να δώσει οποιονδήποτε ακέραιο από το 1 έως το 100.
Πολλές καταστάσεις του πραγματικού κόσμου είναι πολύ λιγότερο απλές από αυτό.
Ως αποτέλεσμα, οι στατιστικολόγοι έχουν δημιουργήσει μια μεγάλη ποικιλία από distributions για να περιγράψουν μαθηματικά τα αποτελέσματα του "πραγματικού κόσμου".
Η συνάρτηση rand() που περιγράφηκε παραπάνω χρησιμοποιείται όταν όλες οι πιθανότητες είναι ίσες.
Αυτή ονομάζεται κατανομή [uniform][uniform-distribution].
Επίσης γνωστή ως "κανονική" κατανομή ή "κωδωνοειδής" καμπύλη, είναι ένας πολύ συνηθισμένος τρόπος να περιγράφουμε την ανακρίβεια σε μετρημένες τιμές.
Για παράδειγμα, υπόθεσε ότι το εργοστάσιο όπου δουλεύεις μόλις αγόρασε 10.000 μπουλόνια που υποτίθεται ότι είναι πανομοιότυπα.
Θέλεις να ρυθμίσεις το ρομπότ του εργοστασίου ώστε να τα χειρίζεται, οπότε ζυγίζεις ένα δείγμα 100 τεμαχίων και βρίσκεις ότι έχουν μέσο (ή mean) βάρος 4,731g.
Είναι εξαιρετικά απίθανο αυτό να σημαίνει ότι όλα ζυγίζουν ακριβώς 4,731g.
Ίσως βρεις ότι οι τιμές κυμαίνονται από 4,627 έως 4,794g αλλά συγκεντρώνονται γύρω από τα 4,731g.
Αυτή είναι η Gaussian distribution, στην οποία οι πιθανότητες κορυφώνονται στη μέση τιμή και μειώνονται συμμετρικά και προς τις δύο πλευρές (εξ ου και "κωδωνοειδής").
Για να το προσομοιώσουμε σε λογισμικό, χρειαζόμαστε κάποιον τρόπο να προσδιορίσουμε το πλάτος της καμπύλης (συνήθως, τα ακριβά μπουλόνια συγκεντρώνονται πιο σφιχτά γύρω από τη μέση τιμή από τα φθηνά!).
Κατά σύμβαση, αυτό γίνεται με την standard deviation: μικρές τιμές για μια οξεία, στενή καμπύλη, μεγάλες για μια χαμηλή, πλατιά καμπύλη.
Οι μαθηματικοί λατρεύουν τα ελληνικά γράμματα, γι' αυτό χρησιμοποιούμε το μ ('mu') για τη μέση τιμή και το σ ('sigma') για την τυπική απόκλιση.
Έτσι, αν διαβάσεις ότι "το 95% των τιμών βρίσκονται εντός 2σ από το μ" ή ότι "το μποζόνιο Higgs ανιχνεύτηκε με βεβαιότητα 5 σίγμα", τέτοια σχόλια σχετίζονται με την τυπική απόκλιση.
Θα υπάρχουν περισσότερα να πούμε γι' αυτό στην Έννοια Statistics.
randn()
Συντομογραφία του "random normal", μοιάζει με την παραλλαγή κινητής υποδιαστολής της rand(), με τη διαφορά ότι οι τιμές κατανέμονται ως Γκάους με μέση τιμή 0 και τυπική απόκλιση 1.
Και πάλι, μπορεί να θέλεις να κλιμακώσεις την ακατέργαστη έξοδο της randn για την τυπική απόκλιση και να τη μετατοπίσεις για τη μέση τιμή.
Το παρακάτω παράδειγμα μετατρέπει σε μέση τιμή 30 και τυπική απόκλιση 5.
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
Είναι δύσκολο να καταλάβεις κοιτάζοντας την έξοδο ότι η ακατέργαστη έξοδος συγκεντρώνεται πιο κοντά στο μηδέν απ' ό,τι σε μια ομοιόμορφη κατανομή. Αν αμφιβάλλεις, δημιούργησε 1000 ή περισσότερες και σχεδίασέ τις για να γίνει πιο προφανές.
Random
Η ενότητα αυτή περιέχει το επόμενο επίπεδο λειτουργικότητας, το οποίο παραλείφθηκε από το Base για να βοηθήσει στην ελαχιστοποίηση του μεγέθους της προεπιλεγμένης διαμόρφωσης της Julia.
Η Random συμπληρώνει τις rand και randn του Base με παραλλαγές που μεταβάλλουν τα δεδομένα, τις rand! και randn!.
Μια χρήσιμη προσθήκη είναι η randstring, η οποία παράγει μια συμβολοσειρά δεδομένου μήκους.
Από προεπιλογή, χρησιμοποιεί κεφαλαία και πεζά γράμματα καθώς και τα ψηφία 0 έως 9, αλλά μπορούν να οριστούν και άλλες επιλογές.
julia> using Random
julia> randstring(20)
"BoJnIxrS33pJiWggXZQV"
Επιπλέον, υπάρχει μια συνάρτηση bitrand για να παράγει έναν τυχαίο BitArray δεδομένου μήκους.
julia> bitrand(5)
julia> bitrand(5)
5-element BitVector:
1
1
0
0
1
Για να ανακατέψουμε τυχαία τα στοιχεία ενός Vector, έχουμε τη shuffle; επίσης τη shuffle! για να μεταβάλει το διάνυσμα εισόδου επί τόπου.
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)
Μερικές φορές είναι χρήσιμο να έχεις αντ' αυτού τις ανακατεμένες θέσεις.
Για αυτό, χρησιμοποίησε τη randperm(n), όπου n είναι το μήκος της ακολουθίας.
julia> randperm(6)
6-element Vector{Int64}:
6
2
4
1
3
5
Στην πράξη, το παραπάνω παράδειγμα δίνει τα ίδια αποτελέσματα με το shuffle(1:6).
Σχετικές συναρτήσεις περιλαμβάνουν τη randsubseq για την εξαγωγή στοιχείων με σταθερή πιθανότητα και τη randcycle για κυκλικές μεταθέσεις.
Αυτές απαιτούν κάποιες εξειδικευμένες γνώσεις, οπότε συμβουλέψου την τεκμηρίωση αν σε ενδιαφέρουν.
Αρκετοί αλγόριθμοι γεννήτριας τυχαίων αριθμών (RNG) είναι ενσωματωμένοι στη Random ως στάνταρ, και οποιοσδήποτε έχει τις κατάλληλες μαθηματικές ικανότητες μπορεί να προσθέσει κι άλλους.
Τέτοια πράγματα ξεφεύγουν πολύ από το πεδίο αυτού του εγγράφου!
Ένας πιο συνηθισμένος λόγος για να δουλεύεις με RNG είναι να ορίσεις ένα seed, το οποίο έχει ως αποτέλεσμα να γίνεται η ακολουθία των "τυχαίων" εξόδων αναπαραγώγιμη από τη μια εκτέλεση στην επόμενη.
Μια τέτοια αναπαραγωγιμότητα δεν είναι κατάλληλη σε κώδικα παραγωγής, αλλά μπορεί να βοηθήσει στις δοκιμές και την αποσφαλμάτωση.
Έξω από το Exercism, υπάρχουν πολλά εγκαταστάσιμα πακέτα που σχετίζονται με την τυχαιότητα, την πιθανότητα και τη στατιστική.
Για περισσότερες πληροφορίες, δες την Έννοια Statistics.
StatsBase.jl
Οι περισσότερες από τις συναρτήσεις του StatsBase είναι αρκετά τεχνικές και δεν σχετίζονται με αυτό το έγγραφο.
Η εξαίρεση είναι η StatsBase.sample, η οποία παρέχει μια πλήρη υλοποίηση δειγματοληψίας με ή χωρίς επανατοποθέτηση (δες μια προηγούμενη ενότητα, παραπάνω).
Υπάρχουν επίσης συναρτήσεις για σταθμισμένη (μη ομοιόμορφη) δειγματοληψία.
Distributions.jl
Οι κατανομές uniform και normal (ή Γκάους) περιγράφηκαν παραπάνω.
Η ενότητα Random περιέχει επίσης τη randexp για δειγματοληψία από την εκθετική κατανομή, η οποία σχετίζεται με την (πολύ συνηθισμένη) κατανομή Poisson.
Για μια πολύ ευρύτερη γκάμα επιλογών, υπάρχει το πακέτο Distributions.jl για όσους έχουν κατάλληλο υπόβαθρο στη στατιστική.