Διαδρομές
/
Julia
Julia
/
Ασκήσεις
/
Η μήτρα του Exercism
Η μήτρα του Exercism

Η μήτρα του Exercism

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

Εισαγωγή

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

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

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

Ορολογία: Ακολουθώντας αιώνες μαθηματικής παράδοσης, αναφερόμαστε στους μονοδιάστατους πίνακες ως Vectors και στους δισδιάστατους πίνακες ως Matrices.

Τα παραδείγματα σε αυτό το κείμενο θα είναι ως επί το πλείστον πίνακες. Η εργασία με 3 ή περισσότερες διαστάσεις είναι συντακτικά σχεδόν ίδια, αλλά η έξοδος είναι δύσκολη και μπερδεμένη στην ανάγνωση (σε μια δισδιάστατη οθόνη).

Κατασκευή πινάκων

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

julia> v = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> w = [1; 2; 3]
3-element Vector{Int64}:
 1
 2
 3

julia> v == w
true

Αν χρησιμοποιήσουμε το κενό (ή το tab) ως διαχωριστικό, το αποτέλεσμα είναι διαφορετικό.

julia> u = [1 2 3]
1×3 Matrix{Int64}:
 1  2  3

Η Julia τώρα το ορίζει αυτό ως Matrix 1×3 (σε άλλα πλαίσια, θα το λέγαμε row vector).

Γενικά, τα κενά ενώνουν τα πράγματα οριζόντια, ενώ οι άνω τελείες (ή οι αλλαγές γραμμής) τα ενώνουν κάθετα.

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

julia> [v 2v]
3×2 Matrix{Int64}:
 1  2
 2  4
 3  6

julia> [v; 2v]
6-element Vector{Int64}:
 1
 2
 3
 2
 4
 6

Υπάρχουν οι συναρτήσεις hcat() και vcat() που κάνουν το ίδιο πράγμα, κάνοντας πιο σαφές ότι πρόκειται για οριζόντιες και κάθετες συνενώσεις. Η γενίκευση σε περισσότερες διαστάσεις είναι η συνάρτηση cat().

jjulia> hcat(v, 2v)
3×2 Matrix{Int64}:
 1  2
 2  4
 3  6

julia> vcat(v, 2v)
6-element Vector{Int64}:
 1
 2
 3
 2
 4
 6

Η πληκτρολόγηση ρητών πινάκων γίνεται βολικά με σειρά γραμμών, γιατί αυτό ταιριάζει με την ανθρώπινη διαίσθηση (πιο εύκολο στο μάτι, για κουλτούρες με οριζόντια γραφή):

julia> m = [1 2 3; 4 5 6]
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

Ωστόσο, να ξέρεις ότι η Julia (όπως η Fortran, η R και η Matlab, αλλά σε αντίθεση με τη C/C++ ή τη NumPy) αποθηκεύει πίνακες N διαστάσεων με σειρά στηλών, και αυτό μπορεί να κάνει τεράστια διαφορά στην απόδοση αν κάνεις βρόχο πάνω στα στοιχεία. Βοήθησε την κρυφή μνήμη της CPU να σε βοηθήσει κι αυτή!

# put these integers in 2 rows and 3 columns

julia> reshape(collect(1:6), 2, 3)
2×3 Matrix{Int64}:
 1  3  5
 2  4  6

Το παραπάνω παράδειγμα παίρνει τους ακεραίους από το 1 έως το 6 και γεμίζει με αυτούς έναν Matrix 2×3 κατά στήλες.

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

julia> zeros(2, 3)  # see also ones()
2×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0

julia> falses(2, 2)  # booleans, see also trues()
2×2 BitMatrix:
 0  0
 0  0

julia> rand(Float32, 2, 3)  # random numbers in the interval [0, 1)
2×3 Matrix{Float32}:
 0.768823  0.169633  0.632565
 0.388451  0.109176  0.850381

Πρόσβαση με θέσεις

Για έναν δισδιάστατο πίνακα, χρησιμοποιούμε γενικά δύο θέσεις με σειρά [row, col].

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> m[1, 2] # row 1, col 2
2

# Stay within bounds! There is no row 3.
julia> m[3, 1]
ERROR: BoundsError: attempt to access 2×3 Matrix{Int64} at index [3, 1]

julia> m[3]
2

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

Η εξήγηση πηγαίνει πίσω στο σχόλιο για τη σειρά στηλών: η Julia κατεβαίνει τη στήλη 1, μετά τη στήλη 2, μέχρι να βρει το 3ο στοιχείο στη μνήμη.

Πρόσεχε: αυτό έχει κάποιες χρήσεις όταν γράφεις βιβλιοθήκες γενικής χρήσης, αλλά πιο πιθανό είναι να σε μπερδέψει!

Ένα παρόμοιο ζήτημα προκύπτει όταν ρωτάς το μέγεθος ενός πίνακα. Η length() δίνει τον συνολικό αριθμό των στοιχείων, η size() δίνει μια πλειάδα με ndims() στοιχεία, με το μήκος κάθε διάστασης.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> length(m)
6

julia> size(m)  # 2 rows, 3 cols
(2, 3)

julia> ndims(m)  # how many dimensions? Like `m |> size |> length`
2

Πρόσβαση με διαστήματα και πίνακες

Μπορούμε εύκολα να αντιγράψουμε έναν υποπίνακα.

Στο παρακάτω παράδειγμα, η συνάρτηση reshape() εξαναγκάζει έναν πίνακα στις καθορισμένες διαστάσεις γεμίζοντάς τον κατά στήλες, και μετά τον τεμαχίζουμε.

julia> m12 = reshape(collect(1:12), 4, 3)
4×3 Matrix{Int64}:
 1  5   9
 2  6  10
 3  7  11
 4  8  12

julia> m12[2:4, 1:2]
3×2 Matrix{Int64}:
 2  6
 3  7
 4  8

# some rows, all columns
julia> m12[2:4, :]
3×3 Matrix{Int64}:
 2  6  10
 3  7  11
 4  8  12

Ένα : μόνο του σημαίνει "αντίγραψε τα πάντα σε αυτή τη διάσταση".

Χρησιμοποίησε διανύσματα για μη συνεχόμενες γραμμές/στήλες:

julia> m12[[1, 3], :]  # rows 1 and 3
2×3 Matrix{Int64}:
 1  5   9
 3  7  11

Εφαρμογή συναρτήσεων σε έναν πίνακα

Έχουμε δει στο παρελθόν συναρτήσεις συνάθροισης όπως οι sum() και maximum() να εφαρμόζονται σε μονοδιάστατες συλλογές, όπου λειτουργούν σε όλα τα στοιχεία και επιστρέφουν ένα βαθμωτό αποτέλεσμα.

Αυτό λειτουργεί και σε περισσότερες διαστάσεις. Ωστόσο, μπορεί να θέλουμε να εφαρμόσουμε τη συνάρτηση σε μία μόνο διάσταση, για παράδειγμα αθροίζοντας κάθετα ή οριζόντια, ώστε να επιστραφεί ένας πίνακας με singleton dimension μεγέθους 1.

Για αυτό, υπάρχει ένα προαιρετικό όρισμα με τη λέξη-κλειδί dims.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> sum(m)  # sum everything
21

julia> sum(m; dims=1)  # sum down
1×3 Matrix{Int64}:
 5  7  9

julia> sum(m; dims=2)  # sum across
2×1 Matrix{Int64}:
  6
 15

# other aggregation functions are similar
julia> maximum(m; dims=2)
2×1 Matrix{Int64}:
 3
 6

Η τιμή του dims είναι η διάσταση που γίνεται μεμονωμένη, οπότε στα παραπάνω παραδείγματα dims=1 => 1×3 και dims=2 => 2×1.

Κατ' επέκταση, οι πίνακες περισσότερων διαστάσεων μπορούν να ανάγουν πολλαπλές διαστάσεις, αν το dims οριστεί σε έναν πίνακα ή ένα διάστημα (και πάλι, η κατανόηση της εξόδου μπορεί να χρειαστεί λίγη σκέψη!).

Γράφοντας συναρτήσεις με επίγνωση των διαστάσεων

Το όρισμα με τη λέξη-κλειδί dims είναι συνηθισμένο σε ενσωματωμένες συναρτήσεις όπως η sum(), αλλά πώς γράφουμε κάτι αντίστοιχο στον δικό μας κώδικα;

Μια καλή απάντηση είναι να χρησιμοποιήσεις συναρτήσεις ανώτερης τάξης όπως η reduce(), και αυτό θα καλυφθεί με κάποιες λεπτομέρειες σε μια επόμενη έννοια.

Εναλλακτικά, η Julia παρέχει αρκετές συναρτήσεις που μας επιτρέπουν να αντιμετωπίζουμε πίνακες N διαστάσεων σαν να ήταν εμφωλευμένα διανύσματα διανυσμάτων.

Για τους πίνακες, οι eachrow() και eachcol() είναι βολικές, αλλά η πιο γενική συνάρτηση είναι η eachslice(), που μπορεί να χειριστεί αυθαίρετες διαστάσεις.

# m is as in the previous examples

julia> eachrow(m)
2-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}:
 [1, 2, 3]
 [4, 5, 6]

julia> eachcol(m)
3-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}:
 [1, 4]
 [2, 5]
 [3, 6]

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

Μια τέτοια όψη μπορεί να χρησιμοποιηθεί για βρόχους, για broadcasting, ή για όλες τις άλλες πράξεις που είδαμε στο πρόγραμμα σπουδών μέχρι τώρα.

Επίσης, οι comprehensions μπορούν να είναι ισχυρές και ευέλικτες με είσοδο πίνακα. Απλές περιπτώσεις αναφέρθηκαν στην έννοια Loops, αλλά θα υπάρξει εκτενέστερη συζήτηση σε μια επόμενη έννοια.

Broadcasting σε πολλαπλές διαστάσεις

Συζητήσαμε τη μονοδιάστατη περίπτωση στην έννοια Vector Operations.

julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
 1.2
 1.5
 1.7

julia> v .- 0.5
3-element Vector{Float64}:
 0.7
 1.0
 1.2

Ο τελεστής με τελείες .- αντιμετωπίζει το μεμονωμένο 0.5 ως ισοδύναμο με το [0.5, 0.5, 0.5], κάνοντάς το broadcasting ώστε να ταιριάζουν οι διαστάσεις, και μετά κάνει την αφαίρεση στοιχείο προς στοιχείο.

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

Για παράδειγμα, όταν κάνουμε broadcasting στον πολλαπλασιασμό ενός διανύσματος γραμμής 2x1 [1.0 1.5 2.0] με τον πίνακα 2x3 [1 2 3; 4 5 6], είναι ισοδύναμο με το να κάνουμε broadcasting του [1.0 1.5 2.0] σε [1.0 1.5 2.0; 1.0 1.5 2.0], με τον πολλαπλασιασμό να γίνεται μετά στοιχείο προς στοιχείο.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> m .* [1.0 1.5 2.0]  # broadcast a 1x3 row vector down
2×3 Matrix{Float64}:
 1.0  3.0   6.0
 4.0  7.5  12.0

julia> m .* [1.0 1.5 2.0; 1.0 1.5 2.0]  # 2x3 elementwise multiplication
2×3 Matrix{Float64}:
 1.0  3.0   6.0
 4.0  7.5  12.0

julia> m ./ [1, 2]  # broadcast a 2x1 column vector across
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 2.0  2.5  3.0

julia> m ./ [1 1 1; 2 2 2]  # 2x3 elementwise division
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 2.0  2.5  3.0

Σημείωση: Για το broadcasting, οι διαστάσεις που δεν είναι μεμονωμένες πρέπει να ταιριάζουν σε μέγεθος (π.χ. 2x3 Matrix .* 2x1 Matrix).

Επιπλέον, μπορεί να γίνει broadcasting μιας συνάρτησης σε κάθε στοιχείο ενός Matrix, ακριβώς όπως γίνεται με ένα Vector.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> (x -> x^2).(m)  # broadcast a function to all elements
2×3 Matrix{Int64}:
  1   4   9
 16  25  36

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

Οδηγίες

Μια μήτρα κουκκίδων είναι μια δισδιάστατη εικόνα που αποτελείται από κουκκίδες και κενό διάστημα. Οι κουκκίδες σχηματίζουν την επιθυμητή εικόνα πάνω στο φόντο του κενού διαστήματος.

Μπορούμε να αποθηκεύσουμε και να επεξεργαστούμε μια εικόνα μήτρας κουκκίδων στη μνήμη ως μια δισδιάστατη Matrix. Στη συνέχεια, το "κενό διάστημα" στις μήτρες θα συμβολίζεται με 0 και οι "κουκκίδες" θα είναι μη μηδενικές.

1. Όρισε τη Matrix του λογότυπου του Exercism

Η Matrix έχει ως εξής, με το 0 ως κενό διάστημα και το 1 ως κουκκίδα:

[
    0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0;
    0 1 0 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
    0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0;
    0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0;
    0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
]

Αν την "αποδώσεις", μοιάζει κάπως έτσι:

  XX          XX  
 X              X 
 X   X      X   X 
 X  X X    X X  X 
 X              X 
X                X
 X    X    X    X 
 X     X  X     X 
 X      XX      X 
 X              X 
  XX          XX

2. Όρισε συναρτήσεις που κάνουν το λογότυπο να κατσουφιάσει

Όρισε τις συναρτήσεις frown!() και frown(), οι οποίες δέχονται ως είσοδο τη Matrix του λογότυπου του Exercism. Επίστρεψε μια Matrix όπου το χαμογελαστό στόμα έχει αλλάξει σε κατσουφιασμένο.

Η Matrix που προκύπτει θα αποδίδεται κάπως έτσι:

  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X      XX      X
 X     X  X     X
 X    X    X    X
 X              X
  XX          XX

3. Συναρμολόγησε έναν τοίχο αυτοκόλλητων

Όρισε τη συνάρτηση stickerwall(), η οποία δέχεται ως είσοδο τη μήτρα του Exercism. Επίστρεψε μια Matrix της μήτρας κουκκίδων που αποδίδεται ως εξής:

  XX          XX    XX          XX
 X              X  X              X
 X   X      X   X  X   X      X   X
 X  X X    X X  X  X  X X    X X  X
 X              X  X              X
X                XX                X
 X    X    X    X  X      XX      X
 X     X  X     X  X     X  X     X
 X      XX      X  X    X    X    X
 X              X  X              X
  XX          XX    XX          XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  XX          XX    XX          XX
 X              X  X              X
 X   X      X   X  X   X      X   X
 X  X X    X X  X  X  X X    X X  X
 X              X  X              X
X                XX                X
 X      XX      X  X    X    X    X
 X     X  X     X  X     X  X     X
 X    X    X    X  X      XX      X
 X              X  X              X
  XX          XX    XX          XX

4. Άλλαξε τις κουκκίδες σε πλήθος pixel ανά στήλη

Δεν περιοριζόμαστε στη χρήση του 1 ως κουκκίδας, οπότε μπορούμε να κωδικοποιήσουμε και άλλες χρήσιμες πληροφορίες, αν το θέλουμε. Όρισε τη συνάρτηση colpixelcount(), η οποία δέχεται ως είσοδο οποιαδήποτε μήτρα κουκκίδων με κουκκίδες 1. Επίστρεψε μια μήτρα κουκκίδων ίδιου μεγέθους, όπου οι κουκκίδες σε κάθε στήλη είναι ο αριθμός των κουκκίδων σε αυτή τη στήλη.

Με τη Matrix του λογότυπου του Exercism ως είσοδο, το αποτέλεσμα είναι μια Matrix ως εξής:

[
    0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    0 8 0 0 0 1 0 0 0 0 0 0 1 0 0 0 8 0;
    0 8 0 0 1 0 2 0 0 0 0 2 0 1 0 0 8 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
    0 8 0 0 0 0 2 0 0 0 0 2 0 0 0 0 8 0;
    0 8 0 0 0 0 0 1 0 0 1 0 0 0 0 0 8 0;
    0 8 0 0 0 0 0 0 1 1 0 0 0 0 0 0 8 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
]

5. Απόδωσε μια μήτρα κουκκίδων

Αντί να μιλάμε απλώς για την απόδοση των δημιουργιών μας, θα θέλαμε να μπορούμε να το κάνουμε και στην πράξη, για να τις βλέπουμε εύκολα. Όρισε τη συνάρτηση render(), η οποία δέχεται ως είσοδο μια μήτρα κουκκίδων. Επίστρεψε μια συμβολοσειρά όπου οι κουκκίδες αποδίδονται ως 'X', τα 0 ως ' ', και χαρακτήρες αλλαγής γραμμής συνδέουν κάθε σειρά.

julia> render(E)
"  XX          XX  \n X              X \n X   X      X   X \n X  X X    X X  X \n X              X \nX                X\n X    X    X    X \n X     X  X     X \n X      XX      X \n X              X \n  XX          XX  "

Όταν εκτυπωθεί, θα πρέπει να αποδίδει τη Matrix του Exercism όπως περιμένουμε.

julia> print(render(E))
  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X    X    X    X
 X     X  X     X
 X      XX      X
 X              X
  XX          XX

Αυτό θα πρέπει να λειτουργεί και με "κουκκίδες" διαφορετικές από το 1:

julia> print(render(colpixelcount(E)))
  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X    X    X    X
 X     X  X     X
 X      XX      X
 X              X
  XX          XX
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Julia Exercism

Έτοιμος να ξεκινήσεις την άσκηση Η μήτρα του Exercism;

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