Διαδρομές
/
Julia
Julia
/
Ασκήσεις
/
Ημερολόγιο καπετάνιου
Ημερολόγιο καπετάνιου

Ημερολόγιο καπετάνιου

Άσκηση εκμάθησης

Εισαγωγή

Πολλά προγράμματα χρειάζονται (φαινομενικά) τυχαίες τιμές για να προσομοιώσουν συμβάντα του πραγματικού κόσμου.

Συνήθη και οικεία παραδείγματα περιλαμβάνουν:

  • Ρίψη νομίσματος: μια τυχαία τιμή από ('H', 'T').
  • Ρίψη ζαριού: έναν τυχαίο ακέραιο από το 1 έως το 6.
  • Ανακάτεμα μιας τράπουλας: μια τυχαία σειρά από μια λίστα με κάρτες.

Η παραγωγή πραγματικά τυχαίων τιμών με έναν υπολογιστή είναι ένας εκπληκτικά δύσκολος τεχνικός στόχος, οπότε μπορεί να δεις αυτά τα αποτελέσματα να αναφέρονται ως "ψευδοτυχαία".

Σημαντικό: Αυτή η Έννοια δεν καλύπτει κρυπτογραφικά ασφαλείς τυχαίους αριθμούς, που αποτελούν ένα πολύ δυσκολότερο πρόβλημα.

Ωστόσο, καλά σχεδιασμένες βιβλιοθήκες όπως η ενότητα Random στην τυπική βιβλιοθήκη της Julia είναι γρήγορες, ευέλικτες και δίνουν αποτελέσματα που αρκούν απόλυτα για τις περισσότερες εφαρμογές σε μοντελοποίηση, προσομοίωση και παιχνίδια.

Η Julia χωρίζει τη λειτουργικότητα της τυχαιότητας σε πολλαπλά σημεία:

  • Λίγες μόνο βασικές αλλά πολύ ευέλικτες συναρτήσεις στο Base, που είναι πάντα διαθέσιμες.
  • Ένα ευρύτερο φάσμα επιλογών στην ενότητα Random.
  • Πιο εξειδικευμένη λειτουργικότητα σε πακέτα που πρέπει να εγκατασταθούν πριν από τη χρήση (και δεν είναι διαθέσιμα στο Exercism).

Η 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

Για διαφορετικό εύρος, απλώς μετατόπισε και κλιμάκωσε κατάλληλα το αποτέλεσμα.

Το παρακάτω παράδειγμα χρησιμοποιεί broadcasting για την αφαίρεση, που καλύπτεται στην Έννοια Πράξεις με Διανύσματα. Το .- εφαρμόζει απλώς αυτή την αριθμητική πράξη σε κάθε στοιχείο του διανύσματος.

# 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. Βάζουμε ξανά την πρώτη μπάλα στο σακούλι και το κουνάμε καλά πριν τραβήξουμε άλλη. Ο αριθμός των μπαλών είναι τώρα ίδιος όπως πριν (7), και η αναλογία κόκκινων προς πράσινες είναι επίσης ίδια.
  2. Βάζουμε την πρώτη μπάλα στο τραπέζι πριν τραβήξουμε μια δεύτερη. Τώρα υπάρχουν μόνο 6 μπάλες στο σακούλι, και η αναλογία κόκκινων:πράσινων εξαρτάται από το χρώμα της πρώτης μπάλας.

Το σενάριο 1 είναι με επανατοποθέτηση, το σενάριο 2 χωρίς, και δίνουν διαφορετικά αποτελέσματα.

Για να προσομοιώσεις δειγματοληψία χωρίς επανατοποθέτηση στη Julia, υπάρχουν μερικές επιλογές.

Η απλούστερη (και στο Exercism η μόνη επιλογή), χρησιμοποίησε τη Random.shuffle() για να βάλεις τα στοιχεία σε τυχαία σειρά και μετά πάρε τα πρώτα n στοιχεία. Αυτό είναι εντάξει για μικρά προβλήματα, αλλά μπορεί να μην κλιμακώνεται καλά σε μεγάλες συλλογές: η shuffle χρειάζεται να παράγει ολόκληρο τον πίνακα, ακόμα κι αν θέλεις μόνο ένα μικρό μέρος του.

Για να κάνεις δειγματοληψία με επανατοποθέτηση "σωστά", εγκατέστησε το πακέτο StatsBase.jl. Αυτό παρέχει τη συνάρτηση sample() με ένα πλήρες φάσμα επιλογών.

Μπορούμε εύλογα να ελπίζουμε ότι παρόμοια λειτουργικότητα θα προστεθεί στη Random σε κάποια μελλοντική έκδοση, ώστε να γίνει μέρος της τυπικής βιβλιοθήκης (τα δείγματα κώδικα σε αυτό το κείμενο δοκιμάστηκαν με Julia 1.11).

Εργασία με Κατανομές

Μέχρι τώρα έχουμε επικεντρωθεί σε περιπτώσεις όπου όλα τα αποτελέσματα είναι εξίσου πιθανά. Για παράδειγμα, η rand(1:100) είναι εξίσου πιθανό να δώσει οποιονδήποτε ακέραιο από το 1 έως το 100.

Πολλές καταστάσεις του πραγματικού κόσμου είναι πολύ λιγότερο απλές από αυτό. Ως αποτέλεσμα, οι στατιστικολόγοι έχουν δημιουργήσει μια μεγάλη ποικιλία από distributions για να περιγράψουν μαθηματικά τα αποτελέσματα του "πραγματικού κόσμου".

Ομοιόμορφες κατανομές

Η συνάρτηση rand() που περιγράφηκε παραπάνω χρησιμοποιείται όταν όλες οι πιθανότητες είναι ίσες. Αυτή ονομάζεται κατανομή uniform.

Κατανομή Gauss

Γνωστή και ως "κανονική" κατανομή ή "κωδωνοειδής" καμπύλη, αυτή είναι ένας πολύ συνηθισμένος τρόπος να περιγράψουμε την ανακρίβεια σε μετρούμενες τιμές.

Για παράδειγμα, ας υποθέσουμε ότι το εργοστάσιο όπου δουλεύεις μόλις αγόρασε 10.000 μπουλόνια που υποτίθεται ότι είναι πανομοιότυπα. Θέλεις να ρυθμίσεις το ρομπότ του εργοστασίου να τα χειρίζεται, οπότε ζυγίζεις ένα δείγμα 100 κομματιών και βρίσκεις ότι έχουν μέσο (ή mean) βάρος 4,731g. Είναι εξαιρετικά απίθανο αυτό να σημαίνει ότι όλα ζυγίζουν ακριβώς 4,731g. Ίσως διαπιστώσεις ότι οι τιμές κυμαίνονται από 4,627 έως 4,794g αλλά συγκεντρώνονται γύρω από το 4,731g.

Αυτή είναι η Gaussian distribution, για την οποία οι πιθανότητες κορυφώνονται στον μέσο όρο και μειώνονται συμμετρικά και από τις δύο πλευρές (εξ ου και "κωδωνοειδής"). Για να το προσομοιώσουμε σε λογισμικό, χρειαζόμαστε κάποιον τρόπο να ορίσουμε το πλάτος της καμπύλης (συνήθως, τα ακριβά μπουλόνια συγκεντρώνονται πιο στενά γύρω από τον μέσο όρο από τα φθηνά!).

Κατά σύμβαση, αυτό γίνεται με τη standard deviation: μικρές τιμές για μια απότομη, στενή καμπύλη, μεγάλες για μια χαμηλή, πλατιά καμπύλη. Οι μαθηματικοί λατρεύουν τα ελληνικά γράμματα, οπότε χρησιμοποιούμε το μ ("μι") για να αναπαραστήσουμε τον μέσο όρο και το σ ("σίγμα") για να αναπαραστήσουμε την τυπική απόκλιση. Έτσι, αν διαβάσεις ότι "το 95% των τιμών βρίσκεται εντός 2σ από το μ" ή "το μποζόνιο Higgs ανιχνεύτηκε με βεβαιότητα 5 σίγμα", τέτοια σχόλια σχετίζονται με την τυπική απόκλιση.

Θα υπάρχουν περισσότερα να πούμε για αυτό στην Έννοια Statistics.

Η συνάρτηση randn()

Συντομογραφία του "random normal", είναι παρόμοια με την παραλλαγή κινητής υποδιαστολής της rand(), με τη διαφορά ότι οι τιμές κατανέμονται κατά Gauss με μέσο όρο 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).

Οδηγίες

Η Mary είναι μεγάλη θαυμάστρια της τηλεοπτικής σειράς Star Trek: The Next Generation. Παίζει συχνά παιχνίδια ρόλων με χαρτί και μολύβι, όπου αυτή και οι φίλοι της υποδύονται το πλήρωμα του Starship Enterprise. Ο χαρακτήρας της Mary είναι ο Πλοίαρχος Picard, που σημαίνει ότι πρέπει να κρατάει το ημερολόγιο του πλοιάρχου. Λατρεύει το δημιουργικό κομμάτι του παιχνιδιού, αλλά δεν της αρέσει να βγάζει τυχαία δεδομένα επιτόπου.

Βοήθησε τη Mary δημιουργώντας γεννήτορες τυχαίων δεδομένων για δεδομένα που εμφανίζονται συχνά στο ημερολόγιο του πλοιάρχου.

1. Δημιούργησε έναν τυχαίο πλανήτη

Το Starship Enterprise συναντά πολλούς πλανήτες στα ταξίδια του. Οι πλανήτες στο σύμπαν του Star Trek χωρίζονται σε κατηγορίες με βάση τις ιδιότητές τους. Για παράδειγμα, η Γη είναι πλανήτης κλάσης M. Όλες οι δυνατές κλάσεις πλανητών είναι: D, H, J, K, L, M, N, R, T και Y.

Υλοποίησε τη συνάρτηση random_planet. Θα πρέπει να επιστρέφει τυχαία μία από τις κλάσεις πλανητών.

julia> random_planet_class()
'K'

2. Δημιούργησε έναν τυχαίο αριθμό νηολογίου διαστημοπλοίου

Το Enterprise (αριθμός νηολογίου NCC-1701) δεν είναι το μόνο διαστημόπλοιο που πετάει εκεί έξω! Όταν συναντιέται με άλλο διαστημόπλοιο, η Mary πρέπει να καταγράψει τον αριθμό νηολογίου αυτού του διαστημοπλοίου.

Οι αριθμοί νηολογίου ξεκινούν με το πρόθεμα "NCC-" και μετά χρησιμοποιούν έναν αριθμό από το 1000 έως το 9999 (συμπεριλαμβανομένων).

Υλοποίησε τη συνάρτηση random_ship_registry_number που επιστρέφει έναν τυχαίο αριθμό νηολογίου διαστημοπλοίου.

julia> random_ship_registry_number()
"NCC-1947"

3. Δημιούργησε ένα τυχαίο stardate

Τι νόημα έχει ένα ημερολόγιο αν δεν περιλαμβάνει ημερομηνίες;

Ένα stardate είναι ένας αριθμός κινητής υποδιαστολής. Οι περιπέτειες του Starship Enterprise από την πρώτη σεζόν του The Next Generation διαδραματίζονται μεταξύ των stardate 41000.0 και 42000.0. Το "4" αντιστοιχεί στον 24ο αιώνα, το "1" στην πρώτη σεζόν.

Υλοποίησε τη συνάρτηση random_stardate που επιστρέφει έναν αριθμό κινητής υποδιαστολής μεταξύ 41000.0 (συμπεριλαμβανομένου) και 42000.0.

julia> random_stardate()
41458.15721310934

4. Δημιούργησε ένα στρογγυλοποιημένο stardate

Έχουν υπάρξει παράπονα ότι τα stardate έχουν πάρα πολλά δεκαδικά ψηφία, οπότε η Mary αποφασίζει να στρογγυλοποιεί τις εγγραφές του ημερολογίου στο ένα δεκαδικό ψηφίο.

Αν και αυτό θα μπορούσε να γίνει στρογγυλοποιώντας τιμές από το random_stardate(), συνειδητοποιείς ότι υπάρχει μια πολύ απλούστερη προσέγγιση.

Υλοποίησε τη συνάρτηση random_stardate_v2 που επιστρέφει έναν αριθμό κινητής υποδιαστολής μεταξύ 41000.0 και 42000.0 (συμπεριλαμβανομένων), με ένα μόνο δεκαδικό ψηφίο.

julia> random_stardate_v2()
41732.6

5. Διάλεξε μερικά τυχαία διαστημόπλοια από μια λίστα.

Μια εκδοχή του παιχνιδιού περιλαμβάνει αλληλεπίδραση με άλλα διαστημόπλοια, και η Mary χρειάζεται μια επιλογή.

Υπάρχει μια πλήρης λίστα με πιθανά διαστημόπλοια, αλλά σε κάθε παιχνίδι χρειάζονται μόνο μερικά από αυτά, και θα πρέπει να επιλέγονται τυχαία.

Υλοποίησε τη συνάρτηση pick_starships(starships, number_needed), η οποία παίρνει ένα διάνυσμα με μοναδικούς αριθμούς νηολογίου διαστημοπλοίων και επιστρέφει ένα διάνυσμα μήκους number_needed. Οι τιμές του αποτελέσματος θα πρέπει να προέρχονται από το starships, χωρίς διπλότυπα.

julia> pick_starships(["NCC-5011", "NCC-1228", "NCC-7039", "NCC-3978", "NCC-1476"], 2)
2-element Vector{String}:
 "NCC-1228"
 "NCC-5011"

5a. Προαιρετικά, σκέψου άλλους τύπους ορισμάτων.

Περιορίσαμε το όρισμα starships ώστε να είναι διάνυσμα. Πώς θα υλοποιούσες την pick_starships() ώστε να δουλεύει με άλλα επαναλήψιμα, όπως πλειάδες ή σύνολα;

Δεν υπάρχουν tests για αυτό το μέρος.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Julia Exercism

Έτοιμος να ξεκινήσεις την άσκηση Ημερολόγιο καπετάνιου;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Julia με 35 έννοιες128 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.