Κατακερματισμοί και μικτές δομές#
Οι τιμές ενός κατακερματισμού είναι βαθμωτά, οπότε μια τιμή δεν μπορεί κυριολεκτικά να είναι πίνακας ή άλλος κατακερματισμός. Μπορεί ωστόσο να είναι αναφορά σε έναν - και αυτό αρκεί για να κατασκευάσετε κάθε σχήμα εγγραφής που χρειάζονται τα πραγματικά προγράμματα: κατακερματισμοί πινάκων, πίνακες κατακερματισμών, κατακερματισμοί κατακερματισμών, και εμφωλευμένοι συνδυασμοί.
Αυτό το κεφάλαιο διατρέχει τα τρία πιο συνηθισμένα σχήματα. Τα μοτίβα συντίθενται: μόλις έχετε έναν κατακερματισμό πινάκων και έναν πίνακα κατακερματισμών, έχετε δωρεάν «γραμμές εγγραφών με πεδία πολλαπλών τιμών».
Κατακερματισμός πινάκων - ομαδοποίηση τιμών κάτω από ένα κλειδί#
Το παράδειγμα που δίνει το κίνητρο: διαβάζετε γραμμές της μορφής city, country και θέλετε να συλλέξετε, για κάθε χώρα, τη λίστα των πόλεών της.
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA
Το φυσικό σχήμα είναι ένας κατακερματισμός του οποίου τα κλειδιά είναι ονόματα χωρών και οι τιμές είναι αναφορές σε πίνακες πόλεων:
my %cities_by_country;
while (<>) {
chomp;
my ($city, $country) = split /, /;
push @{$cities_by_country{$country}}, $city;
}
for my $country (sort keys %cities_by_country) {
my @cities = sort @{$cities_by_country{$country}};
print "$country: ", join(', ', @cities), ".\n";
}
Δύο πράγματα προς προσοχή:
Το
push @{$cities_by_country{$country}}, $cityλειτουργεί από την πρώτη κιόλας επανάληψη για κάθε νέα χώρα. Η εγγραφή hash δεν υπάρχει ακόμη, οπότε η Perl δημιουργεί έναν ανώνυμο πίνακα για αυτήν, αποθηκεύει μια αναφορά, και σπρώχνει μέσα την πόλη. Αυτό είναι πάλι αυτο-δημιουργίακαι ο λόγος που σχεδόν ποτέ δεν χρειάζεστε το
$h{$k} = [] unless exists $h{$k}ως ξεχωριστή γραμμή.
Η αποαναφορά
@{$cities_by_country{$country}}είναι μηχανική: «ο πίνακας στον οποίο δείχνει αυτή η τιμή του κατακερματισμού». Όπου θα γράφατε@citiesαν ήταν κατονομασμένος πίνακας, γράφετε τη μορφή με αγκύλες αντί αυτής.
Η έξοδος είναι:
Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington.
Πίνακας κατακερματισμών - πίνακας εγγραφών#
Όταν κάθε στοιχείο σε μια λίστα κουβαλάει αρκετά κατονομασμένα πεδία, αποθηκεύστε κάθε εγγραφή ως αναφορά κατακερματισμού και βάλτε τις αναφορές σε έναν πίνακα:
my @people = (
{ name => 'Ada', born => 1815, field => 'computing' },
{ name => 'Hedy', born => 1914, field => 'signals' },
{ name => 'Grace', born => 1906, field => 'compilers' },
);
Το $people[0] είναι αναφορά κατακερματισμού· το $people[0]->{name} ή (με τη συντόμευση του βέλους-μεταξύ-δεικτών) το $people[0]{name} είναι 'Ada'.
Τυπικές λειτουργίες:
# Sort by a field
my @by_year = sort { $a->{born} <=> $b->{born} } @people;
# Select rows
my @early = grep { $_->{born} < 1900 } @people;
# Project one field out
my @names = map { $_->{name} } @people;
# Mutate a record in place
$people[1]{field} = 'radio';
Μέσα στις sort, grep, και map η εγγραφή είναι αναφορά κατακερματισμού, οπότε φτάνετε στα πεδία της μέσω του βέλους. Τα $a και $b μέσα σε έναν συγκριτή sort είναι εδώ αναφορές κατακερματισμών, όχι κατακερματισμοί.
Κατακερματισμός κατακερματισμών - πίνακες αναζήτησης με κλειδιά δομημένες τιμές#
Όταν κάθε εγγραφή έχει ως κλειδί ένα μοναδικό αναγνωριστικό, ένας κατακερματισμός κατακερματισμών είναι συνήθως πιο όμορφος από έναν πίνακα κατακερματισμών:
my %people = (
ada => { born => 1815, field => 'computing' },
hedy => { born => 1914, field => 'signals' },
grace => { born => 1906, field => 'compilers' },
);
print $people{ada}{born}; # 1815
$people{grace}{field} = 'COBOL'; # mutate one record
Επαναλάβετε σε καθορισμένη σειρά ταξινομώντας τα κλειδιά:
for my $key (sort keys %people) {
my $rec = $people{$key};
print "$key: born $rec->{born}, $rec->{field}\n";
}
Η σύνδεση της εσωτερικής αναφοράς σε μια κατονομασμένη λεξιλογική ($rec) είναι συνήθεια που αξίζει να αποκτήσετε - κάνει τον κώδικα να διαβάζεται από πάνω προς τα κάτω αντί να επαναλαμβάνει το $people{$key}{field} πέντε φορές.
Μικτό - εγγραφές με πεδία πολλαπλών τιμών#
Συνθέστε τα όλα μαζί: κάθε εγγραφή είναι ένας κατακερματισμός, και ένα από τα πεδία της είναι αναφορά σε πίνακα.
my %library = (
'The Road' => {
author => 'Cormac McCarthy',
year => 2006,
genres => ['fiction', 'post-apocalyptic'],
},
'Thinking in Systems' => {
author => 'Donella Meadows',
year => 2008,
genres => ['non-fiction', 'systems'],
},
);
for my $title (sort keys %library) {
my $rec = $library{$title};
my $genres = join ', ', @{$rec->{genres}};
print "$title ($rec->{year}, $rec->{author}): $genres\n";
}
Το @{$rec->{genres}} είναι το ίδιο μοτίβο όπως πριν: φτάστε μέσα στον κατακερματισμό, πάρτε την αναφορά πίνακα, αποαναφέρετέ την με @{...}.
Ένα πεδίο μπορεί εξίσου εύκολα να κρατά μια αναφορά κώδικα ή ένα εμφωλευμένο hash αναζήτησης. Τίποτα στην πρόσβαση δεν αλλάζει· ακολουθείτε το ίδιο βέλος μέσα σε ό,τι δείχνει το κάθε πεδίο:
my $rec = {
name => 'lookup',
CALLBACK => sub { uc $_[0] },
LOOKUP => { red => '#f00', green => '#0f0' },
};
$rec->{CALLBACK}->('hi'); # call the code ref - 'HI'
$rec->{LOOKUP}{red}; # nested hash lookup - '#f00'
Το $rec->{CALLBACK} είναι μια αναφορά κώδικα, οπότε το ακολουθούν ->($arg) την καλεί. Το $rec->{LOOKUP} είναι μια αναφορά hash, οπότε το {red} δεικτοδοτεί μέσα από αυτήν (το βέλος ανάμεσα στις δύο δεικτοδοτήσεις παραλείπεται).
Η αντιγραφή ενός πίνακα αναφορών αντιγράφει τον πίνακα, όχι τους αναφερόμενους#
Όταν μια εγγραφή κρατά έναν πίνακα αναφορών, η αντιγραφή αυτού του πίνακα με [ @list ] φτιάχνει έναν νέο πίνακα του οποίου τα στοιχεία είναι οι ίδιες αναφορές. Το αντίγραφο και το πρωτότυπο μοιράζονται τους αναφερόμενούς τους, οπότε μια μεταβολή μέσα από μία διαδρομή είναι ορατή μέσα από κάθε διαδρομή που κρατά αυτές τις ίδιες αναφορές στοιχείων:
my %TV = (
simpsons => {
kids => [ { name => 'Bart', age => 10 },
{ name => 'Lisa', age => 8 } ],
},
);
my @also = @{ $TV{simpsons}{kids} }; # array copied; hashes shared
$TV{simpsons}{kids}[0]{age}++; # mutate through one path
print $also[0]{age}; # 11 - the other path sees it too
Το @also είναι ένας ξεχωριστός πίνακας, οπότε το σπρώξιμο πάνω του δεν μεγαλώνει το πρωτότυπο. Αλλά τα στοιχεία του είναι ακριβώς οι ίδιες αναφορές hash, οπότε η αύξηση του age μέσω του $TV{simpsons}{kids}[0] αλλάζει το hash στο οποίο δείχνει επίσης το $also[0]. Για να πάρετε ανεξάρτητες εγγραφές πρέπει να αντιγράψετε ένα επίπεδο βαθύτερα, χτίζοντας φρέσκα hashes αντί να επαναχρησιμοποιείτε τις αναφορές τους.
Έλεγχος του τι έχετε#
Πριν ενεργήσετε σε μια εμφωλευμένη θυρίδα συχνά είναι χρήσιμο να γνωρίζετε αν η θυρίδα υπάρχει πραγματικά εκεί. Χρησιμοποιήστε την exists αντί της defined όταν θέλετε να αποφύγετε την αυτο-δημιουργία κενών ενδιάμεσων δομών:
if (exists $library{$title} and exists $library{$title}{genres}) {
push @{$library{$title}{genres}}, 'essential';
}
Η ανάγνωση του $library{$title}{genres} σε περιβάλλον lvalue - η αριστερή πλευρά μιας ανάθεσης, ή ως όρισμα της push - αυτο-δημιουργεί επίπεδα που λείπουν. Η ανάγνωσή του σε περιβάλλον rvalue όχι. Οι έλεγχοι exists παραπάνω κρατούν τη σάρωση μόνο για ανάγνωση.
Για να ελέγξετε τον τύπο μιας αναφοράς χωρίς να βγάλετε τιμές από αυτή, χρησιμοποιήστε την ref:
ref $library{'The Road'} # HASH
ref $library{'The Road'}{genres} # ARRAY
Πού να πάτε στη συνέχεια#
Ανώνυμες αναφορές - οι κατασκευαστές
[...]/{...}που χρησιμοποιήθηκαν σε όλο αυτό το κεφάλαιο, και πότε μια κατονομασμένη μεταβλητή είναι η καλύτερη επιλογή.Αναφορές υπορουτινών - η εναπομένουσα γεύση αναφοράς, απαραίτητη για πίνακες callback και αποστολή.