Η αποδόμηση αναφέρεται στην πράξη της εξαγωγής των στοιχείων μιας συλλογής, όπως ενός Array ή ενός Hash.
Οι τιμές που αποδομούνται μπορούν στη συνέχεια να ανατεθούν σε μεταβλητές μέσα στην ίδια εντολή.
Η πολλαπλή ανάθεση είναι η δυνατότητα να αναθέτεις πολλές μεταβλητές για να αποδομήσεις τιμές μέσα σε μία εντολή.
Έτσι ο κώδικας γίνεται πιο συνοπτικός και ευανάγνωστος, και γίνεται χωρίζοντας τις μεταβλητές που θα ανατεθούν με κόμμα, όπως first, second, third = [1, 2, 3].
Ο τελεστής splat (*) και ο διπλός τελεστής splat (**) χρησιμοποιούνται συχνά σε περιβάλλοντα αποδόμησης.
Το *<variable_name> και το **<variable_name> δεν πρέπει να συγχέονται με το * και το **.
Ενώ το * και το ** χρησιμοποιούνται για πολλαπλασιασμό και ύψωση σε δύναμη αντίστοιχα, το *<variable_name> και το **<variable_name> χρησιμοποιούνται ως τελεστές σύνθεσης και αποδόμησης.
Η πολλαπλή ανάθεση σου επιτρέπει να αναθέτεις πολλές μεταβλητές σε μία γραμμή.
Για να χωρίσεις τις τιμές, χρησιμοποίησε κόμμα ,:
>> a, b = 1, 2
=> [1, 2]
>> a
=> 1
Η πολλαπλή ανάθεση δεν περιορίζεται σε έναν μόνο τύπο δεδομένων:
>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true
Η πολλαπλή ανάθεση μπορεί να χρησιμοποιηθεί για να ανταλλάξεις στοιχεία σε πίνακες. Αυτή η πρακτική είναι αρκετά συνηθισμένη στους αλγορίθμους ταξινόμησης. Για παράδειγμα:
>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Αυτό είναι επίσης γνωστό ως "Parallel Assignment" και μπορεί να χρησιμοποιηθεί για να αποφύγεις μια προσωρινή μεταβλητή.
Αν υπάρχουν περισσότερες μεταβλητές από τιμές, στις επιπλέον μεταβλητές θα ανατεθεί nil:
>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil
Στη Ruby, είναι δυνατό να αποδομήσεις τα στοιχεία πινάκων/hash σε ξεχωριστές μεταβλητές. Επειδή οι τιμές εμφανίζονται μέσα στους πίνακες με σειρά θέσης, ξεπακετάρονται σε μεταβλητές με την ίδια σειρά:
>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"
Αν υπάρχουν τιμές που δεν χρειάζεσαι, μπορείς να χρησιμοποιήσεις το _ για να δηλώσεις "συλλέχθηκε αλλά δεν χρησιμοποιείται":
>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"
Η αποδόμηση και η ανάθεση τιμών από πίνακες που βρίσκονται μέσα σε έναν πίνακα (γνωστό και ως εμφωλευμένος πίνακας) λειτουργεί με τον ίδιο τρόπο όπως η ρηχή αποδόμηση, αλλά χρειάζεται έκφραση αποδόμησης με οριοθέτες (()) για να διευκρινίσει το πλαίσιο ή τη θέση των τιμών:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"
Μπορείς επίσης να ξεπακετάρεις βαθιά μόνο ένα μέρος ενός εμφωλευμένου πίνακα:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"
Αν η αποδόμηση έχει μεταβλητές με λανθασμένη τοποθέτηση ή/και λανθασμένο αριθμό τιμών, θα πάρεις ένα συντακτικό σφάλμα:
fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
(a, b), (d) = fruits_vegetables
# syntax error, unexpected '=', expecting '.' or &. or :: or '['
((a, b), (d)) = fruits_vegetables
# syntax error, unexpected ')', expecting '.' or &. or :: or '['
Πειραματίσου εδώ και θα παρατηρήσεις ότι αυτό που καθορίζει είναι το πρώτο μοτίβο, όχι οι διαθέσιμες τιμές στη δεξιά πλευρά. Το συντακτικό σφάλμα δεν σχετίζεται με τη δομή δεδομένων.
*)Όταν αποδομείς έναν πίνακα, μπορείς να χρησιμοποιήσεις τον τελεστή splat (*) για να συλλάβεις τις "υπόλοιπες" τιμές.
Αυτό είναι πιο ξεκάθαρο από το να κόψεις τον πίνακα (κάτι που σε ορισμένες περιπτώσεις είναι λιγότερο ευανάγνωστο).
Για παράδειγμα, μπορούμε να εξαγάγουμε το πρώτο στοιχείο και μετά να αναθέσουμε τις υπόλοιπες τιμές σε έναν νέο πίνακα χωρίς το πρώτο στοιχείο:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]
Μπορούμε επίσης να εξαγάγουμε τις τιμές στην αρχή και στο τέλος του πίνακα, ομαδοποιώντας όλες τις τιμές στη μέση:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
Μπορούμε επίσης να χρησιμοποιήσουμε το * στη βαθιά αποδόμηση:
>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]
Hash
Η αποδόμηση ενός hash είναι λίγο διαφορετική από την αποδόμηση ενός πίνακα. Για να μπορέσεις να ξεπακετάρεις ένα hash, πρέπει πρώτα να το μετατρέψεις σε πίνακα. Διαφορετικά δεν θα γίνει καμία αποδόμηση:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Για να μετατρέψεις ένα Hash σε πίνακα, μπορείς να χρησιμοποιήσεις τη μέθοδο to_a:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> fruits_inventory.to_a
=> [[:apple, 6], [:banana, 2], [:cherry, 3]]
>> x, y, z = fruits_inventory.to_a
>> x
=> [:apple, 6]
Αν θέλεις να ξεπακετάρεις τα κλειδιά, μπορείς να χρησιμοποιήσεις τη μέθοδο keys:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple
Αν θέλεις να ξεπακετάρεις τις τιμές, μπορείς να χρησιμοποιήσεις τη μέθοδο values:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6
Η σύνθεση είναι η δυνατότητα να ομαδοποιήσεις πολλές τιμές σε έναν πίνακα που ανατίθεται σε μια μεταβλητή. Είναι χρήσιμη όταν θέλεις να αποδομήσεις τιμές, να κάνεις αλλαγές και μετά να συνθέσεις ξανά τα αποτελέσματα σε μια μεταβλητή. Κάνει επίσης δυνατή τη συγχώνευση 2 ή περισσότερων πινάκων/hash.
*)Η σύνθεση ενός πίνακα μπορεί να γίνει με τον τελεστή splat, (*).
Αυτό θα πακετάρει όλες τις τιμές σε έναν πίνακα.
>> fruits = ["apple", "banana", "cherry"]
>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>> combined_fruits = *fruits, *more_fruits
>> combined_fruits
=> ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
**)Η σύνθεση ενός hash γίνεται με τον διπλό τελεστή splat (**).
Αυτό θα πακετάρει όλα τα ζεύγη κλειδιού/τιμής από ένα hash σε ένα άλλο hash, ή θα συνδυάσει δύο hash μαζί.
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> more_fruits_inventory = {orange: 4, kiwi: 1, melon: 2, mango: 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>> combined_fruits_inventory
=> {:apple=>6, :banana=>2, :cherry=>3, :orange=>4, :kiwi=>1, :melon=>2, :mango=>3}
*) και του διπλού τελεστή splat (**) με μεθόδουςΌταν δημιουργείς μια μέθοδο που δέχεται αυθαίρετο αριθμό ορισμάτων, μπορείς να χρησιμοποιήσεις *arguments ή **keyword_arguments στον ορισμό της μεθόδου.
Το *arguments χρησιμοποιείται για να πακετάρει αυθαίρετο αριθμό ορισμάτων θέσης (χωρίς λέξεις-κλειδιά) και
το **keyword_arguments χρησιμοποιείται για να πακετάρει αυθαίρετο αριθμό ορισμάτων με λέξεις-κλειδιά.
Χρήση του *arguments:
# This method is defined to take any number of positional arguments
# (Using the single line form of the definition of a method.)
>> def my_method(*arguments)= arguments
# Arguments given to the method are packed into an array
>> my_method(1, 2, 3)
=> [1, 2, 3]
>> my_method("Hello")
=> ["Hello"]
>> my_method(1, 2, 3, "Hello", "Mars")
=> [1, 2, 3, "Hello", "Mars"]
Χρήση του **keyword_arguments:
# This method is defined to take any number of keyword arguments
>> def my_method(**keyword_arguments)= keyword_arguments
# Arguments given to the method are packed into a dictionary
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
Αν η μέθοδος που όρισες δεν έχει καμία καθορισμένη παράμετρο για ορίσματα με λέξεις-κλειδιά (**keyword_arguments ή <key_word>: <value>), τότε τα ορίσματα με λέξεις-κλειδιά θα πακεταριστούν σε ένα hash και θα ανατεθούν στην τελευταία παράμετρο.
>> def my_method(a)= a
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
Τα *arguments και **keyword_arguments μπορούν επίσης να χρησιμοποιηθούν σε συνδυασμό μεταξύ τους:
def my_method(*arguments, **keyword_arguments)
p arguments.sum
for (key, value) in keyword_arguments.to_a
p key.to_s + " = " + value.to_s
end
end
my_method(1, 2, 3, a: 1, b: 2, c: 3)
6
"a = 1"
"b = 2"
"c = 3"
Μπορείς επίσης να γράψεις ορίσματα πριν και μετά το *arguments για να επιτρέψεις συγκεκριμένα ορίσματα θέσης.
Αυτό λειτουργεί με τον ίδιο τρόπο όπως η αποδόμηση ενός πίνακα.
Τα ορίσματα πρέπει να είναι δομημένα με συγκεκριμένη σειρά:
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)
Αν δεν ακολουθήσεις αυτή τη σειρά, θα πάρεις ένα σφάλμα.
def my_method(a, b, *arguments)
p a
p b
p arguments
end
my_method(1, 2, 3, 4, 5)
1
2
[3, 4, 5]
Μπορείς να γράψεις ορίσματα θέσης πριν και μετά το *arguments:
>> def my_method(a, *middle, b)= middle
>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]
Μπορείς επίσης να συνδυάσεις ορίσματα θέσης, *arguments, ορίσματα με λέξεις-κλειδιά και **keyword_arguments:
>> def my_method(first, *many, last, a:, **keyword_arguments)
p first
p many
p last
p a
p keyword_arguments
end
>> my_method(1, 2, 3, 4, 5, a: 6, b: 7, c: 8)
1
[2, 3, 4]
5
6
{:b => 7, :c => 8}
Το να γράψεις τα ορίσματα σε λανθασμένη σειρά θα οδηγήσει σε σφάλμα:
def my_method(a:, **keyword_arguments, first, *arguments, last)
arguments
end
my_method(1, 2, 3, 4, a: 5)
syntax error, unexpected local variable or method, expecting & or '&'
... my_method(a:, **keyword_arguments, first, *arguments, last)
Μπορείς να χρησιμοποιήσεις τον τελεστή splat (*) για να ξεπακετάρεις έναν πίνακα ορισμάτων σε μια κλήση μεθόδου:
def my_method(a, b, c)
p c
p b
p a
end
numbers = [1, 2, 3]
my_method(*numbers)
3
2
1
Μπορείς επίσης να χρησιμοποιήσεις τον διπλό τελεστή splat (**) για να ξεπακετάρεις ένα hash ορισμάτων σε μια κλήση μεθόδου:
def my_method(a:, b:, c:)
p c
p b
p a
end
numbers = {a: 1, b: 2, c: 3}
my_method(**numbers)
3
2
1
Ο φίλος σου ο Linus είναι μηχανοδηγός και οδηγεί εμπορευματικά τρένα ανάμεσα σε πόλεις. Αν και είναι καταπληκτικός στο χειρισμό των τρένων, δεν τα καταφέρνει εξίσου καλά με τα logistics ή με τους υπολογιστές. Θα ήθελε να ζητήσει τη βοήθειά σου στον προγραμματισμό για να οργανώσει τις λεπτομέρειες των τρένων και να διορθώσει λάθη στα δεδομένα των διαδρομών.
Αυτή η άσκηση θα μπορούσε εύκολα να λυθεί με τεμαχισμό, ευρετηρίαση και διάφορες μεθόδους hash.
Ωστόσο, θα θέλαμε να εξασκηθείς στο πακετάρισμα, το ξεπακετάρισμα και την πολλαπλή εκχώρηση λύνοντας καθεμία από τις παρακάτω εργασίες.
Ο φίλος σου κρατά αρχείο με το αναγνωριστικό (ID) κάθε βαγονιού, αλλά δεν είναι ποτέ σίγουρος πόσα βαγόνια θα χρειαστεί να επεξεργαστεί το σύστημα κάθε δεδομένη στιγμή. Θα ήταν πολύ πιο εύκολο για το υπόλοιπο πρόγραμμα logistics να έχει αυτά τα δεδομένα συσκευασμένα σε έναν ενιαίο πίνακα.
Υλοποίησε μια μέθοδο generate_list_of_wagons() που δέχεται οποιονδήποτε αριθμό αναγνωριστικών βαγονιών.
Κάθε αναγνωριστικό θα είναι ένας θετικός ακέραιος.
Στη συνέχεια, η μέθοδος θα πρέπει να return τα δοσμένα αναγνωριστικά ως έναν μόνο πίνακα.
LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]
Σε αυτό το σημείο, αρχίζεις να εξοικειώνεσαι με τα δεδομένα και με τον τρόπο που χρησιμοποιούνται στο πρόγραμμα logistics. Το σύστημα αναγνωριστικών δίνει πάντα στη μηχανή το αναγνωριστικό 1, ενώ στα υπόλοιπα βαγόνια του τρένου αναθέτει ένα τυχαία επιλεγμένο αναγνωριστικό μεγαλύτερο του 1.
Ο φίλος σου έπρεπε να συνδέσει δύο νέα βαγόνια στο τρένο και ξέχασε να ενημερώσει το σύστημα! Τώρα, τα δύο πρώτα βαγόνια του πίνακα του τρένου πρέπει να μετακινηθούν στο τέλος, αλλιώς τα πάντα θα είναι σε λάθος σειρά.
Για να γίνουν τα πράγματα πιο περίπλοκα, ο φίλος σου μόλις ανακάλυψε έναν δεύτερο πίνακα που φαίνεται να περιέχει αναγνωριστικά βαγονιών που λείπουν. Το μόνο που θυμάται είναι ότι, αφού μετακινηθούν τα νέα βαγόνια, τα αναγνωριστικά από αυτόν τον δεύτερο πίνακα πρέπει να τοποθετηθούν αμέσως μετά τη μηχανή που έχει οριστεί.
Ο Linus θα σου είναι πολύ ευγνώμων που διόρθωσες τα λάθη του και ενοποίησες τα δεδομένα.
Υλοποίησε μια μέθοδο fix_list_of_wagons() που παίρνει δύο πίνακες με αναγνωριστικά βαγονιών.
Θα πρέπει να μετακινήσει τα δύο πρώτα στοιχεία του πρώτου πίνακα στο τέλος και να εισαγάγει τις τιμές από τον δεύτερο πίνακα πίσω (στη δεξιά πλευρά του) από το αναγνωριστικό της μηχανής (1).
Στη συνέχεια, η μέθοδος θα πρέπει να return έναν πίνακα με τις τροποποιήσεις.
LocomotiveEngineer.fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
# => [1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
Τώρα που όλα τα δεδομένα των βαγονιών είναι σωστά, ο Linus θα ήθελε να ενημερώσεις τις πληροφορίες δρομολόγησης του συστήματος. Κατά μήκος μιας διαδρομής μεταφοράς, ένα τρένο μπορεί να κάνει στάσεις σε μερικούς διαφορετικούς σταθμούς για να παραλάβει ή/και να αφήσει φορτίο. Κάθε ταξίδι μπορεί να έχει διαφορετικό αριθμό από αυτά τα ενδιάμεσα σημεία παράδοσης. Ο φίλος σου θα ήθελε να ενημερώσεις το hash δρομολόγησης του συστήματος με οποιαδήποτε πληροφορία παράδοσης λείπει ή είναι επιπλέον.
Υλοποίησε μια μέθοδο add_missing_stops() που δέχεται ένα hash δρομολόγησης ακολουθούμενο από μεταβλητό αριθμό ορισμάτων με λέξεις-κλειδιά.
Αυτά τα ορίσματα μπορεί να έχουν τη μορφή ενός hash που περιέχει μία ή περισσότερες στάσεις, ή οποιουδήποτε αριθμού ζευγών λέξεων-κλειδιών stop_<number>: "city".
Στη συνέχεια, η μέθοδος θα πρέπει να επιστρέψει το hash δρομολόγησης ενημερωμένο με ένα επιπλέον κλειδί που περιέχει έναν πίνακα με όλες τις στάσεις που προστέθηκαν, με τη σειρά τους.
LocomotiveEngineer.add_missing_stops({from: "New York", to: "Miami"},
stop_1: "Washington, DC", stop_2: "Charlotte", stop_3: "Atlanta",
stop_4: "Jacksonville", stop_5: "Orlando")
# => {from: "New York", to: "Miami", stops: ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
Οι στάσεις θα περνιούνται πάντα με τη σωστή σειρά, οπότε δε χρειάζεται να ανησυχείς για την ταξινόμησή τους.
Ο Linus δουλεύει πάνω στο πρόγραμμα δρομολόγησης και έχει παρατηρήσει ότι ορισμένες διαδρομές λείπουν κάποιες σημαντικές λεπτομέρειες. Οι αρχικές πληροφορίες της διαδρομής έχουν κατασκευαστεί ως ένα hash και ο φίλος σου θα ήθελε να ενημερώσεις αυτό το hash με ό,τι μπορεί να λείπει. Κάθε διαδρομή στο σύστημα απαιτεί ελαφρώς διαφορετικές λεπτομέρειες, οπότε ο Linus θα προτιμούσε πραγματικά μια γενική λύση.
Υλοποίησε μια μέθοδο που ονομάζεται extend_route_information() και δέχεται δύο hash.
Το πρώτο hash περιέχει τις πόλεις αναχώρησης και προορισμού ανάμεσα στις οποίες εκτελείται η διαδρομή του τρένου.
Το δεύτερο hash περιέχει άλλες λεπτομέρειες της διαδρομής, όπως την ταχύτητα, το μήκος ή τη θερμοκρασία του τρένου. Η μέθοδος θα πρέπει να επιστρέψει ένα ενοποιημένο hash με όλες τις πληροφορίες της διαδρομής.
Το δεύτερο hash μπορεί να περιέχει διαφορετικές ή περισσότερες ιδιότητες από αυτές που φαίνονται στο παράδειγμα.
LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Ruby με 21 έννοιες121 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.