Μαθήματα Perl (Μέρος 2): Χειρισμός arrays, hashes και regular expressions

dimitris | Κυρ, 08/25/2013 - 07:46 | 19'

Μήπως φοβόσαστε τους δυσνόητους τελεστές της Perl, τα arrays, τα hashes και τις regular expressions; Σε αυτό το tutorial για προγραμματισμό σε Perl, εξηγούμε τα πάντα - σχεδόν...
 

Πριν ξεκινήσουμε με το ταξίδι μας στο βασίλειο της Perl, θα πρέπει να θυμηθούμε τους τρεις βασικούς τύπους μεταβλητών τα οποία είναι τα scalars, arrays και hashes.

  • Τα scalars ξεκινούν πάντα με το πρόθεμα $,  και κρατάνε μία μοναδική πληροφορία όπως ένα string. Είναι οι πιο κοινές μεταβλητές της Perl.
  • Τα arrays, τα οποία ξεκινούν με ένα @, είναι απλώς αριθμημένα scalars.
  • Τα hashes, με το πρόθεμα #, είναι scalars τα οποία ορίζονται από άλλα scalars, που αναφέρονται ως κλειδιά.

Περισσότερες  πληροφορίες για τις μεταβλητές της Perl, τα arrays και τα hashes μπορείτε να βρείτε στο εισαγωγικό tutorial για προγραμματισμό Perl.

Σε αυτό το άρθρο θα δούμε πως μπορείτε να χειριστείτε τις περισσότερο πολύπλοκες μεταβλητές της Perl, τα arrays και τα hashes, παίρνοντας παράλληλα μια πρώτη γεύση από την αληθινή "μαύρη μαγεία" της Perl, τα regular expressions ή ελληνιστί κανονικές εκφράσεις.

Ταξινόμηση και απαρίθμηση

Όπως ξέρετε τα arrays τοποθετούν σε ακολουθία μια σειρά από scalars. Η σύνταξη των λιστών στην Perl λειτουργεί με παρόμοιο τρόπο. Αυτές είναι ανώνυμες σειρές από scalars περικλειόμενες από παρενθέσεις. Μία καλή χρήση των συντακτικών αυτών είναι να αντιστοιχία τιμών σε δύο ή παραπάνω μεταβλητές με μία μόνο εντολή ή στην αντιμετάθεση τους.

($X,$Y,$Z) = ($Y, $Z, $X) # Κυκλική μετακίνηση
($Name, $Surname, $Phone) = ('John', 'Smith', 5556791);
($DARTH_VADER, @JEDI) = ('Anakin Skywalker','Yoda','ObiWan','Mace Windu');

Οι πρώτες δύο γραμμές είναι αρκετά αυτονόητες. Στην τρίτη εντολή, η λίστα αριστερά αντιπαραβάλλεται με ένα scalar ($DARTH_VADER) και ένα ονομασμένο array (@JEDI). Όλοι γνωρίζουμε τι θα γίνει εάν την αντιστοιχήσουμε στην δεξιά λίστα: Ο νεαρός Anakin θα βρεθει αντιστοιχισμένος με τoν Darth Vader. Αυτό συμβαίνει διότι το $DARTH_VADER, από την στιγμή που είναι scalar, μπορεί να κρατήσει μία μόνο τιμή. Αυτή η τιμή είναι η πρώτη από τους τέσσερις Jedi ιππότες, δηλαδή .ο Anakin Skywalker. Επιπλέον επειδή αυτό που μένει στην αριστερή λίστα είναι ένα array, το @JEDI, όλοι οι άλλοι ιππότες στην δεξιά λίστα αποθηκεύονται εκεί, στην ίδια σειρά.

Ας δούμε τώρα την συνάρτηση splice(), η οποία χρησιμοποιείται για την διαγραφή, προσθήκη ή αντικατάσταση στοιχείων μέσα σε ένα array. Αρχικά θα ορίσουμε μερικούς πλανήτες:

@STAR_WARS_PLANETS = ('Naboo', 'Tatooine', 'Geonosis');

Χρησιμοποιήστε την splice() για να προσθέσετε τους πλανήτες Coruscant και Alderaan μετά από τον Tatooine:

splice(@STAR_WARS_PLANETS, 2,0, ('Coruscant','Alderaan'));

Σε αυτή την γραμμή, η πρώτη παράμετρος είναι το όνομα του array: STAR_WARS_PLANETS. Έπειτα έρχεται ο δείκτης (ξεκινώντας από το μηδέν!) από όπου επιθυμούμε να εφαρμόσουμε τη splice: σε αυτή την περίπτωση, μετά από τον Tatooine. Η τρίτη παράμετρος δεικνύει τον αριθμό των στοιχείων που θέλετε να διαγράψετε. Επειδή δεν σκοπεύουμε να διαγράψουμε τίποτα, αλλά να προσθέσουμε περισσότερους πλανήτες, βάζουμε 0. Η τελευταία προαιρετική παράμετρος είναι η λίστα που θα προστεθεί στην θέση που καθορίσαμε. Εάν δεν συμπεριλάβετε αυτή την παράμετρο, το splice() δεν θα προσθέσει τίποτα.

Εάν θέλετε να ταξινομήσετε στοιχεία κατά αλφαβητική σειρά, η Perl περιλαμβάνει μία sort() συνάρτηση η οποία, από default, θεωρεί όλα τα στοιχεία ως strings, ακόμα και τους αριθμούς. Οπότε παραδείγματος χάρη εάν πληκτρολογήσετε τον παρακάτω κώδικα στο prompt του κέλυφους:

perl -e "@A_LIST = ('Dominions', 180, 3, '10, Syggrou', 'Admiralty'); print join (\"\n\", sort @A_LIST), \"\n\";"

θα πάρετε ως αποτέλεσμα μία αλφαβητικά ταξινομημένη λίστα:

180
3
Admiralty
Dominions
Syggrou

η συνάρτηση sort() μπορεί να ταξινομήσει στοιχεία και με άλλα κριτήρια:

@SORTED_LIST = sort AS_I_WANT @UNORDERED_LIST;

H AS_I_WANT είναι μια υπορουτίνα η οποία δέχεται δύο scalars για είσοδο και επιστρέφει -1, 0 ή 1 ανάλογα με το ποιο από τα δύο είναι πρώτο σύμφωνα με τα κριτήρια που θέλετε.

Υπολογίστε το μέγεθος ενός array

Πως μπορείτε να μάθετε τον αριθμό των στοιχείων ενός array ή hash; Πολύ απλά: απλώς αντιστοιχήστε το σε ένα scalar. Από την στιγμή που το scalar μπορεί να διατηρήσει μόνο μία τιμή, η Perl εισάγει απλώς τον αριθμό των στοιχείων σε αυτό. Ένα παρόμοιο τρικ υπάρχει και στα hashes. Η συνάρτηση keys επιστρέφει ένα array που περιέχει μόνο όλα τα κλειδιά. Οπότε:

$HOW[email protected];
$HOW_MANY_HASH_ITEMS = keys %SOME_HASH;

Ας σταθούμε σε ένα τελευταίο θέμα γύρω από τα arrays. Υπάρχει κάτι το οποίο κανένας Perl hacker δεν μπορεί να ζήσει χωρίς αυτό. Πρόκειται για την πολυαγαπημένη STDIN, την ροή εισόδου για κείμενο που χρησιμοποιείται από οποιοδήποτε "καθωσπρέπει" Unix πρόγραμμα. Ευτυχώς είναι πολύ εύκολο στην χρήση. Το STDIN αναφέρεται εδώ γιατί, πιστέψτε το ή όχι, τα δεδομένα που μεταφέρει μπορούν να φορτωθούν σε ένα array πανέυκολα:

@LINES = <STDIN>;

Ορίστε. Με μία μόνο εντολή, φορτώσατε όλες τις γραμμές του εισαγόμενου κειμένου σε ένα ξεχωριστό @LINES array.

Αγνό hash

Σε προηγούμενο άρθρο για προγραμματισμό σε Perl έγινε μια εισαγωγή στα hashes και στην έννοια της ευρετηριοποίησης scalars μέσω άλλων scalars (κλειδιών). Όπως θα γνωρίζετε πλέον, ένα hash αποτελείται από κλειδιά που αναπαριστούν τιμές. Από την στιγμή που θα έχετε ένα hash, είναι εύκολη η διαχείριση του χρησιμοποιώντας απλώς τα κλειδιά του, αντί για τις τιμές του.

Εάν θέλατε να βάλετε τις τιμές σε μία σταθερή σειρά, απλώς θα χρησιμοποιούσατε ένα απλό array σωστά; Σωστά, γιατί σε αντίθεση με τα arrays, τα hashes δεν είναι τακτοποιημένα σε συγκεκριμένη αριθμητική σειρά, μήτε στην σειρά κατά την οποία προστέθηκαν τα κλειδιά.

Το αποτέλεσμα αυτού είναι πως οι hash τιμές θα πρέπει πάντοτε να απευθύνονται με το κλειδί τους αντί της τιμής του.

Αυτό ισχύει ιδιαίτερα όταν χρειάζεστε να διαγράψετε ένα κλειδί και την αντίστοιχη τιμή του. Ο τρόπος για να γίνει αυτό είναι μέσω της delete(), η οποία παίρνει για παράμετρο το κλειδί του στοιχείου που θέλετε να διαγράψετε. Η χρήση αυτής της συνάρτησης είναι αναγκαία επειδή εάν π.χ. απλώς αντιστοιχήσετε μία κενή τιμή σε ένα κλειδί, δεν θα διαγραφόταν τίποτε. Το στοιχείο θα υπήρχε ακόμα, απλώς με μία κενή τιμή.

Ορισμένες φορές, το πρώτο πράγμα που θα πρέπει να κάνετε με ένα στοιχείο σε ένα hash είναι να βρείτε εάν υπάρχει καν και εάν έχει αντιστοιχηθεί σε αυτό οποιαδήποτε τιμή. Για να το κάνετε αυτό, η Perl παρέχει δύο συναρτήσεις με το όνομα exists() και defined(), όπου χρησιμοποιούνται ως έχει:

if exists ($STAR_WARS_ACTORS{'Leia'}) { # κάνε κάτι…};
if defined ($STAR_WARS_ACTORS{'Leia'}) { # κάνε κάτι άλλο…};

H πρώτη εντολή εκτελείται μόνο εάν το hash περιλαμβάνει ένα κλειδί ίσο με το string 'Leia', άσχετα από το τι περιέχει η αντίστοιχη τιμή του.

Η δεύτερη πάει λίγο παραπέρα: θα επιστρέψει αληθές μόνο εάν υπάρχει ένα κλειδί ίσο με το 'Leia' και εάν η αντίστοιχη τιμή του έχει οριστεί από πριν.

Regular expressions

Για την διαχείριση μεγάλων ποσοτήτων κειμένου, η Perl ανέπτυξε ίσως το πληρέστερο και ισχυρότερο σύνολο regular expressions (κανονικών εκφράσεων) από όλες τις γλώσσες προγραμματισμού. Ένα regular expression, ή regex για συντομία, είναι μία περιγραφή της δομής ενός string με ένα "custom" συντακτικό. Η περιγραφή αποτελείται από συνηθισμένο κείμενο και ειδικούς μεταχαρακτήρες που αντιστοιχούν στα χαρακτηριστικά του string. Ορισμένους από αυτούς τους χαρακτήρες μπορείτε να τους δείτε στο ειδικό "σκονάκι για regular expressions στην Perl"  λίγο παρακάτω.

Η ομορφιά καθώς και ο απόλυτος σκοπός των regular expressions είναι πως από την στιγμή που μπορείτε να περιγράψετε οποιοδήποτε string με πλήρη λεπτομέρεια, μπορείτε επίσης να πείτε στο script να βρει συγκεκριμένα μοτίβα και να τα επεξεργαστεί αυτόματα. Αυτό εξηγείται καλύτερα με τα παρακάτω παραδείγματα:

/Jedi/
/\bJedi\b/
/^Jedi$/
/Jedi/i
/Jedi|Sith/

Η πρώτη regex είναι αληθής όποτε ένα string περιέχει τη λέξη Jedi, πιθανότατα σαν ένα μέρος μιας μεγαλύτερης λέξης. Εάν θέλετε να βρείτε την λέξη 'Jedi' ως μοναδική, περικλείστε με (\b) όπως φαίνεται στο 2ο παράδειγμα. Η τρίτη regex είναι περισσότερο ακριβής: θα επιστρέψει true μόνο εάν το 'Jedi' βρίσκεται στην αρχή (^) και στο τέλος ($) μιας γραμμής, ή με λίγα λόγια όταν το 'Jedi' είναι η μοναδική λέξη σε μια γραμμή. 

Στην Perl, ένα regex είναι πάντα case-sensitive, οπότε αντί να κάθεστε να γράφετε JEDI|jedi|JedI κλπ, απλώς χρησιμοποιήστε την παράμετρο i, όπως στο παράδειγμα της τέταρτης γραμμής. Τέλος, η τελευταία εντολή επιστρέφει αληθές όποτε βρει είτε την λέξη 'Jedi' είτε την 'Sith'.

Είναι σημαντικό να τα γνωρίζετε όλα αυτά, διότι εάν η Perl βρει ένα pattern κειμένου που περιγράφεται από το regex που της δώσατε, μπορεί έπειτα να εκτελέσει οποιεσδήποτε ενέργειες θέλετε πάνω σε αυτό.

Παραδείγματος χάρη:

if ($STRING =~ m/μία regex εδώ/) {κάνε κατι}
$STRING = ~s/μία regex έδω/κάποιο pattern κειμένου/;

H regex περικλείεται από τις καθέτους. Τα strings αντιστοιχίζονται σε αυτή μέσω του =~ τελεστή. Όταν ένας m χαρακτήρας προηγείται των καθέτων σημαίνει  'ταιριάζει αυτό το string με αυτή την regex;'. Όποτε υπάρχει ένα s αντί για ένα m και ορισμένο κείμενο μεταξύ των καθέτων μετά του regex, σημαίνει 'πάρε το $STRING και μέσα σε αυτό αντικατέστησε το regex με οτιδήποτε είναι γραμμένο μεταξύ των δύο τελευταίων καθέτων'.

Τα regex μπορούν να περιέχουν scalars και να αποθηκεύουν τα αποτελέσματα τους σε ειδικές μεταβλητές. Αυτό τα κάνει αρκετά ευέλικτα εργαλεία.

$JEDI = 'Anakin';
s/Master $JEDI/The future Darth Vader/g;
s/Master (Obi-Wan|Yoda)/The Jedi Knight $1/;

Σε αυτό το παράδειγμα όλες οι περιστάσεις του 'Master Anakin (Anakin, επειδή η Perl θα αντικαταστήσει την μεταβλητή $JEDI  με την τρέχουσα τιμή της) θα αντικατασταθούν με το 'The future Darth Vader'. H παράμετρος g στο τέλος σημαίνει globally: χωρίς αυτήν, θα αντικατασταθεί μόνο η πρώτη περίσταση και όχι όλες.

Η δεύτερη regex επιδεικνύει ένα άλλο ενδιαφέρον χαρακτηριστικό. Βρίσκει ένα ταίριασμα όποτε ένας από τους δύο Jedi που αναφέρονται, ονομάζεται Master. Από την στιγμή που τα ονόματα βρίσκονται ανάμεσα στις παρενθέσεις, δεν ξεχνιούνται όταν βρεθούν, αλλά αποθηκεύονται στην ειδική $1 μεταβλητή. Άρα η ίδια regex θα αντικαταστήσει το 'The Jedi Knight Yoda' με το 'Master Yoda' ή το 'The Jedi Knight Obi-Wan' με το 'Master Obi-Wan'. Εάν πρέπει να μνημονευθούν περισσότερα ταιριάσματα αντιστοιχίζονται με τον ίδιο τρόπο σε $2, $3 κ.ο.κ μέχρι το $9.

Σκονάκι για τις regular expressions

Αυτή είναι μία λίστα με τους συνηθέστερους ειδικούς χαρακτήρες που υπάρχουν στα regular expressions της Perl. Κρατήστε το, θα γλυτώσετε αρκετό χρόνο!

.      Οποιοσδήποτε μοναδικός χαρακτήρας εκτός από newline
^     Αρχή ενός string
$    Τέλος ενός string
*     Μηδέν ή παραπάνω περιστάσεις του προηγούμενου χαρακτήρα
+     Μία η παραπάνω περιστάσεις του προηγ. Χαρακτήρα
?     Μηδέν ή μία περιστάσεις του προηγ. χαρακτήρα
\n    Νέα γραμμή
\t     Tab
\w   Ψηφία και αλφαβητικά, πεζά ή κεφαλαία
\W   Οποιοσδήποτε χαρακτήρας που δεν είναι χαρακτήρας ή ψηφίο
\d    Ψηφία από 0 μέχρι 9
\D   Οτιδήποτε εκτός από ψηφία
\s    Κενοί χαρακτήρες, space, tab, newline
\S   Οποιοιδήποτε μη κενοί χαρακτήρες
\b    Όριο λέξεως
|      Εναλλακτική μεταξύ δύο τιμών (π.χ. A|B).
[]     Αγκύλες που οριοθετούν μία κλάση χαρακτήρων
()    Παρενθέσεις που θυμούνται το περικλειόμενο substring

Σημείωση: Όταν χρειάζεται κυριολεκτικά να ταιριάξετε έναν από τους παραπάνω χαρακτήρες, π.χ. το + , θα πρέπει να προηγείται από ένα blackslash για να διαχωριστεί από τον regex ρόλο του.

Α++ # ταιριάζει ένα η παραπάνω κεφαλαία A
\+ # ταιριάζει έναν χαρακτήρα +
\++ # ταιριάζει έναν ή παραπάνω χαρακτήρα +

 

Διαβάστε περισσότερα για regular expressions

Η Perl πιθανότατα να έχει περισσότερες regular expressions από οποιαδήποτε άλλη γλώσσα προγραμματισμού. Εάν θέλετε να συντάξετε τις δικές σας, το απόλυτο βιβλίο-πηγή είναι το Mastering Regular Expressions από τον Jeffrey Friedl (O'Reilly, 2002).

 

Δείτε ακόμα:

Δώσε αστέρια!

MO: 5 (ψήφοι: 1)