Με τον όρο "κανονικές εκφράσεις", αναφερόμαστε σε γενικευμένες σειρές χαρακτήρων που μπορούν να χρησιμοποιηθούν σε αναζητήσεις, αντικαταστάσεις και χειρισμούς μοτίβων χαρακτήρων (patterns). Η χρήση των κανονικών εκφράσεων συντομεύει, ομαδοποιεί και επιταχύνει λεξικογραφικές διαδικασίες. O τρόπος με τον οποίο αναγνωρίζονται, αντικαθίστανται και χρησιμοποιούνται από την Perl είναι ο βασικός λόγος που η γλώσσα είναι τόσο δημοφιλής και χρηστική σε προβλήματα επεξεργασίας κειμένου.
Τι είναι; Ένα μοτίβο είναι μια έκφραση που αντιστοιχεί σε μια οικογένεια πραγματικών string. H αντιστοίχιση γίνεται στη βάση συγκεκριμένων κανόνων και συντακτικών δομών που περιγράφονται στη συνέχεια. Κατ' αυτόν τον τρόπο ένα μοτίβο μπορεί να κωδικοποιηθεί σε μια κανονική έκφραση, η οποία πλέον μπορεί να λειτουργήσει ως μια τυπική γραμματική (formal grammar). H τυπικές γραμματικές είναι στην ουσία σύνολα κανόνων για την παραγωγή ενός αριθμού σειρών χαρακτήρων (string). Ένα απλό παράδειγμα δίνεται στη συνέχεια:
Έστω το σύνολο των κανόνων:
Τότε η παράσταση-μοτίβο: CATNAA μπορεί να παράξει τις παρακάτω αλληλουχίες:
CATAAA, CATGAA, CTAAA, CTGAA
Mε αυτόν τον τρόπο μπορούμε να πούμε ότι:
Η γραμματική των κανόνων 1,2 για το CATNAA μπορεί να παράξει τα (CATAAA, CATGAA, CTAAA, CTGAA ) ή εναλλακτικά ότι τα (CATAAA, CATGAA, CTAAA, CTGAA) είναι παράγωγα του CATNAA μέσω των κανόνων 1,2.
Τα μοτίβα (motifs/patterns) στην Perl δεν διαφέρουν ουσιαστικά από την παραπάνω σύλληψη. Πρόκειται ουσιαστικά για σειρές χαρακτήρων με ιδιαίτερη κωδικοποίηση που βασίζεται σε ένα σύνολο κανόνων. Ο συνδυασμός των κανόνων μεταφράζεται από την Perl σε μια γραμματική και σε όλα τα πιθανά παράγωγά της. Τα παράγωγα αυτά στη συνέχεια χρησιμοποιούνται σε λεξικογραφικές διαδικασίες (αναζητήσεις, αντικαταστάσεις κλπ).
H κωδικοποίηση ενός μοτίβου μέσα από ένα σύνολο κανόνων στην Perl γίνεται με τη χρήση των κανονικών εκφράσεων (regular expressions, ή regex για συντομία).
Στη συνέχεια θα δούμε αναλυτικά τους κανόνες που μπορούμε να χρησιμοποιήσουμε. Αρχικά θα δούμε τη χρήση ειδικών χαρακτήρων, στη συνέχεια τον τρόπο με τον οποίον αναζητούμε ή αντικαθιστούμε μοτίβα και τέλος τρόπους για την χρήση πολλών μοτίβων ταυτόχρονα.
Eίναι χαρακτήρες που χρησιμοποιούνται για τον συμβολισμό κανόνων και ως εκ τούτου χρειάζεται ιδιαίτερη προσοχή στη χρήση τους σε οποιαδήποτε λεξικογραφική διαδικασία. Θυμηθείτε ότι δεν χρησιμοποιούμε τους:
. * ? + [ ] ( ) { } ^ $ | \
σε ονόματα αρχείων και μεταβλητών.
Οι πιο πάνω ειδικοί χαρακτήρες είναι μέρος της σύνταξης μοτίβων και μεταφράζονται από την Perl σαν κάτι άλλο από τον χαρακτήρα. Παρακάτω θα δούμε καθέναν από αυτούς πιο αναλυτικά.
Είναι σύμβολα που ρυθμίζουν τον αριθμό των επαναλήψεων χαρακτήρων ή συνόλων χαρακτήρων σε ένα μοτίβο. Είναι ποσοτικοί τροποποιητές.
Αστερίσκος "*"
Είναι τελεστής πολλαπλότητας και σημαίνει ότι μπορούμε να αναπαράγουμε όσες φορές θέλουμε (από 0, χωρίς πάνω όριο) [0, άπειρο] από τον αμέσως προηγούμενο χαρακτήρα. Έτσι το μοτίβο:
Α*
μπορεί να παράξει τα "","Α", "ΑΑ", "ΑΑΑ" κ.ο.κ. Προσοχή στο "", που σημαίνει ότι το Α* μπορεί να μεταφραστεί και ως "κανένα Α".
Σύμβολο πρόσθεσης "+"
Είναι τελεστής πολλαπλότητας που διαφέρει από τον * στο γεγονός ότι το όριο πολλαπλότητας εδώ είναι [1,άπειρο] δηλαδή τα strings που παράγει είναι αυτά που παράγει το * εκτός του κενού string "":
το:
Α+
μπορεί να παράξει τα "Α", "ΑΑ", "ΑΑΑ" κ.ο.κ.
Λατινικό ερωτηματικό "?"
Είναι επίσης τελεστής πολλαπλότητας και σημαίνει "μία ή καμία φορές" από αυτό που προηγείται. Το:
Α?
δηλαδή παράγει τα "" και "Α".
Άγκιστρα {}
Μπορούμε να επιλέξουμε ακριβώς τα όρια του αριθμού των φορών που επιτρέπεται να επαναληφθεί o αμέσως προηγούμενος χαρακτήρας χρησιμοποιώντας τα άγκιστρα με δύο ορίσματα m και n που υποδηλώνουν το κάτω και το πάνω όριο. Έτσι η σύνταξη X{m,n} σημαίνει πως θα το μοτίβο μπορεί να παράξει σειρές από "Χ" μήκους από m έως n (εννοείται ότι m<n). Π.χ. το:
Α{3 , 6}
παράγει τα "ΑΑΑ", "ΑΑΑΑ", "ΑΑΑΑΑ" και "ΑΑΑΑΑΑ".
Δεν είναι υποχρεωτική η χρήση και των δύο ορίων. Το πάνω όριο μπορεί να παραλειφθεί ώστε το μοτίβο να μεταφραστεί σε "τουλάχιστον m" επαναλήψεις.
Α{3 , }
που παράγει "ΑΑΑ", "ΑΑΑΑ" ...κ.ο.κ χωρίς πάνω όριο, ενώ χρήση ενός μόνο ακεραίου εντός αγκίστρων σημαίνει "ακριβώς m" επαναλήψεις
Α{6}
παράγει "ΑΑΑΑΑΑ".
Είναι σύμβολα που χαρακτηρίζουν ποιοτικούς κανόνες και που μπορούν να παράξουν διαφορετικές σειρές χαρακτήρων σε ό,τι αφορά τη σύσταση και όχι το πλήθος ή το μέγεθος (μόνο).
Τελεία "."
Σημαίνει οποιοσδήποτε χαρακτήρας εκτός από αλλαγή γραμμής. Κατ' αυτόν τον τρόπο το μοτίβο:
Α.GC
αντιστοιχεί σε τέσσερις χαρακτήρες εκ των οποίων ο δεύτερος μπορεί να είναι οποιοδήποτε γράμμα, κεφαλαίο ή πεζό, αριθμός ή σύμβολη. Μπορεί δηλαδή να παράξει όλες τις παραπάνω σειρές χαρακτήρων.
Aγκύλες […]
H εισαγωγή συγκεκριμένων χαρακτήρων μέσα σε αγκύλες συγκεκριμενοποιεί την παραγωγή παραλλαγών για το μοτίβο. Έτσι το το μοτίβο:
Α[AGCT]GC
μπορεί να παράξει τα εξής τέσσερα strings AAGC, AGGC, ACGC και ATGC, μπορεί δηλαδή να δεχτεί στη θέση του δεύτερου χαρακτήρα οποιονδήποτε από τους τέσσερις χαρακτήρες A,G,C,T.
Άρνηση Αγκυλών [^...]
Στην περίπτωση που θέλουμε να δημιουργήσουμε ένα μοτίβο στο οποίο θα απαγορεύεται η χρήση συγκεκριμένων χαρακτήρων, ενσωματώνουμε το σύμβολο της άρνησης ^ σαν πρώτο χαρακτήρα μέσα στις αγκύλες. Η χρήση του ^ μόνο κατ' αυτόν τον τρόπο συμβολίζει την εξαίρεση όλων των μοναδικών χαρακτήρων που ακολουθούν μέσα στις αγκύλες από το μοτίβο. Έτσι το μοτίβο:
Α[^ΑGCT]GC
μπορεί να παράξει όλες τις σειρές τεσσάρων χαρακτήρων που ΔΕΝ είναι τα AAGC, AGGC, ACGC και ATGC.
Διάζευξη με κάθετο "|"
Το σύμβολο της καθέτου χρησιμοποιείται για τη διάζευξη (θυμηθείτε το συμβολισμό της διπλής καθέτου "||" για το "or" στους λογικούς ελέγχους). Η διάζευξη ορίζεται μέσα σε παρενθέσεις για όσο μεγάλο πλήθος χαρακτήρων επιθυμούμε. Το προηγούμενο παράδειγμα μπορεί να γραφτεί και ως έξης:
Α(A|G|C|T)GC
Η πιο πάνω έκφραση είναι πανομοιότυπη της A[AGCT]GC. Η διαφορά της διάζευξης με τη χρήση των αγκυλών είναι ότι επιτρέπει συνδυασμούς string στη διάζευξη αντί για απλούς χαρακτήρες, κάτι που θα δούμε πιο αναλυτικά στη συνέχεια. (βλ. Ομαδοποίηση)
"Στην αρχή" ^
Χρησιμοποιούμε το σύμβολο ^ εκτός αγκυλών για να δηλώσουμε ότι το μοτίβο θα ξεκινά με τους χαρακτήρες που περιέχει. ´Ετσι το μοτίβο:
^AGCT
θα παράξει μόνο σειρές χαρακτήρων που θα ξεκινούν με το ΑGCT αλλά που δεν επιτρέπεται να το περιέχουν. Η διαφορά είναι λεπτή εδώ και έχει περισσότερο να κάνει με τον τρόπο με τον οποίο αναζητούμε μοτίβα παρά με τη λογική της παραγωγής τους από μια γραμματική. Θα φανεί καλύτερα σε παραδείγματα που ακολουθούν
"Στο τέλος" $
Το σύμβολο του δολλαρίου σε κανονικές εκφράσεις είναι το ακριβώς αντίθετο του ^. Έτσι το μοτίβο:
ΑGCT$
θα παράξει σειρές χαρακτήρων που τελειώνουν σε AGCT. Προσέξτε πώς η θέση των ^ και $ σε σχέση με το string είναι δηλωτική της λειτουργίας τους. Το ^ μπαίνει πριν από το string και το $ μετά από αυτό.
Σημειώνεται εδώ ότι το $ δεν αναγνωρίζει την αλλαγή γραμμής \n ως τελευταίο χαρακτήρα αλλά τον αμέσως προηγούμενο από αυτήν.
Πέρα από τους παραπάνω ειδικούς χαρακτήρες, υπάρχουν συνδυασμοί χαρακτήρων που χρησιμοποιούνται για να δηλώσουν γενικότερες ομάδες συγγενικών χαρακτήρων. Έτσι μπορούμε να χρησιμοποιήσουμε τους εξής συμβολισμούς:
Η ομαδοποίηση και σύλληψη (capture) των μοτίβων είναι βασικές λειτουργίες των κανονικών εκφράσεων. Γίνονται με τη χρήση παρενθέσεων οι οποίες μας δίνουν τη δυνατότητα:
Ομαδοποίηση
Η ομαδοποίηση γίνεται μέσα στο μοτίβο, τοποθετώντας παρενθέσεις γύρω από τους χαρακτήρες που θέλουμε να ομαδοποιήσουμε. Έτσι πχ. το μοτίβο:
Α(AG)+Τ
μπορεί να παράξει αλληλουχίες που ξεκινούν με Α ακολουθούμενο από ένα ή περισσότερα AG και τελειώνουν με Τ, δηλ. AAGT, AAGAGT, AAGAGAGT κ.ο.κ. Βλέπουμε εδώ ένα πρώτο παράδειγμα συνδυασμού συμβόλων και ομαδοποίησης. Οι χαρακτήρες μέσα στην παρένθεση ομαδοποιούνται και αντιμετωπίζονται ως σύνολο από το σύμβολο που ακολουθεί (εδώ το +). Συνδυασμοί αυτού του τύπου μπορούν να γίνουν και να είναι αρκετά πολύπλοκοι (συχνά σε βαθμό που να χρειάζεται ιδιαίτερη προσοχή). Π.χ:
A(A.G)?T.*C$
Mπορείτε να εξηγήσετε σε τι αντιστοιχεί το παραπάνω μοτίβο;
Σύλληψη (capture)
Η σύλληψη μοτίβου είναι η διαδικασία με την οποία η εύρεση ενός μοτίβου συνδέεται αυτόματα με την αποθήκευσή του σε μια μεταβλητή. Για να καταλάβουμε καλύτερα την έννοια αυτή θα πρέπει να δούμε ένα πρακτικό παράδειγμα χρήσης των μοτίβων. Φανταστείτε μια αναζήτηση ενός απλού string σε ένα μεγαλύτερο string, σαν κι αυτές που έχουμε δει ως τώρα. Π.χ.
#! /usr/bin/perl use warnings; $string="AAGCAACACCGCTACGGCGGCTTTAG"; $pattern="GCTA"; while ($string =~ /$pattern/g) { # Pattern Search here $times++; } print "Found it $times time(s)\n";
Θυμηθείτε πως η συντακτική δομή Β =~ /Α/ αντιστοιχεί στην αναζήτηση του Α εντός του Β. Σημειώστε τώρα πως το Α, το στοιχείο αναζήτησης δηλαδή, δεν είναι απαραίτητα ένα απλό string αλλά μπορεί να είναι μια κανονική έκφραση, ένα μοτίβο δηλαδή που θα "περιέχει", θα μπορεί να παράξει μια σειρά από strings και που η Perl θα τα αναζητήσει όλα εντός του Β πριν επιστρέψει την απάντηση. Έτσι η αναζήτηση μπορεί να γίνει πιο πολύπλοκη ώστε να αναζητά περισσότερα strings.
#! /usr/bin/perl use warnings; $string="AAGCAACACCGCTACGGCGGCTTTAG"; $pattern="GCT*A"; while ($string =~ /$pattern/g) { # Pattern Search here $times++; } print "Found it $times time(s)\n";
Στην δεύτερη αυτή περίπτωση το μοτίβο που αναζητούμε είναι πια μια κανονική έκφραση "GCT*A". Η Perl αναζητά στο $string όλα τα παράγωγα της. Έτσι καταγράφει τρεις επιτυχίες. Μπορείτε να δείτε ποιες είναι αυτές;
Στο παραπάνω παράδειγμα βλέπουμε μια ακόμα δυνατότητα που μας παρέχει η σύνταξη της Perl. Αυτή είναι η αναζήτηση μοτίβων που έχουν αποδοθεί σε μεταβλητές. Δεν είναι δηλαδή απαραίτητο να αναζητήσουμε το GCT*A απευθείας στην γραμμή αναζήτησης, αλλά μπορούμε πρώτα να το έχουμε αποδώσει σε μια μεταβλητή και να χρησιμοποιήσουμε αυτήν. Η γραμμή της αναζήτησης είναι ταυτόσημη με την:
while ($string =~ /GCT*A/g) {
Ας έρθουμε τώρα στο θέμα της "σύλληψης" του μοτίβου. Πολύ συχνά θα χρειαστεί να αναζητήσουμε ένα μοτίβο το οποίο θα θέλουμε να αποθηκεύσουμε σε μια μεταβλητή για χρήση σε άλλες διεργασίες. Αυτό επιτυγχάνεται πολύ εύκολα με τη χρήση παρενθέσεων μέσα στην εντολή αναζήτησης.
#! /usr/bin/perl use warnings; $string="AAGCAACACCGCTACGGCGGCTTTAG"; $pattern="GCT*A"; while ($string =~ /($pattern)/g) { # Pattern Search AND Capture here $times++; print "Success#$times: Found $1\n"; # Use of $1 as captured pattern }
Στο πιο πάνω παράδειγμα έχουν γίνει μερικές αλλαγές σε σχέση με τα παραπάνω. Αρχικά το $pattern έχει μπει μέσα σε παρενθέσεις και στη συνέχεια η εντολή εκτύπωσης αναφέρεται σε μια μεταβλητή που συμβολίζεται με το $1. Τι έχει συμβεί εδώ; Αρχικά το $pattern όχι μόνο αναζητήθηκε αλλά ζητήθηκε από το πρόγραμμα να "συλλαμβάνεται" σε κάθε περίπτωση που εντοπίζεται. Η Perl αποδίδει τα "συλληφθέντα" μοτίβα σε βαθμωτές μεταβλητές στις οποίες αποδίδει αυτόματα έναν αύξοντα αριθμό με βάση τη σειρά τους στην κανονική έκφραση. Στο παράδειγμά μας υπάρχει μόνο ένα μοτίβο το οποίο αποδίδεται αυτόματα στην μεταβλητή $1. Η εκτύπωσή της ΔΕΝ αποδίδει το $pattern αλλά το ΑΚΡΙΒΕΣ string με το οποίο ταυτίστηκε, δηλαδή το κομμάτι εκείνο της σειράς χαρακτήρων στόχου που αναζητούμε.
Στο επόμενο παράδειγμα μπορείτε να δείτε πώς λειτουργεί η απόδοση πολλαπλών μεταβλητών σε διαφορετικά μοτίβα που ομαδοποιούνται και συλλαμβάνονται μέσω παρενθέσεων.
#! /usr/bin/perl use warnings; $string="AAGCAACACCGCTACGGCGGCTTTAG"; if ($string =~ /((^A.{7})(C+G*C+)(T+))/) { # Pattern Search AND Capture here $times++; print "Success#$times: Found $1\n"; print "Success#$times: Found $2\n"; print "Success#$times: Found $3\n"; print "Success#$times: Found $4\n"; }
Δείτε προσεκτικά τη γραμμή αναζήτησης. Υπάρχουν τέσσερα ζεύγη παρενθέσεων, ένα για καθεμία από τις τρεις διαφορετικές ομάδες ^A.{7}, C+G*C+ και T+ και μια που περικλείει και τις τρείς ομάδες σε μια μεγάλη ομάδα που καλύπτει όλη την κανονική έκφραση. Τώρα δείτε καλύτερα τα αποτελέσματα που τυπώνονται:
1. Το πρώτο print τυπώνει μια μεγάλη παράσταση που περιέχει και τα τρία μοτίβα
2. Το δεύτερο τυπώνει το match, την ταύτιση που έγινε για το ^Α.{7}
3. Ομοίως το τρίτο τυπώνει την ταύτιση για το C*G*C+ και
4. Το τέταρτο τυπώνει ένα Τ
Από τα παραπάνω θα πρέπει να έχετε καταλάβει τη διαδοχή των αποδόσεων μεταβλητών. Η μεγαλύτερη και περιεκτικότερη παρένθεση από αριστερά είναι η πρώτη ($1) . Η αρίθμηση των μεταβλητών γίνεται σταδιακά καθώς προχωράμε από αριστερά προς τα δεξιά.
Προσοχή! Ένα στοιχείο που πρέπει να κρατήσετε είναι πως το match, η ταύτιση δηλαδή γίνεται για όλο το μοτίβο ^A.{7}C+G*C+Τ+ και όχι ξεχωριστά για τις επιμέρους ομάδες του. Η αναζήτηση είναι επιτυχής επειδή βρίσκει ένα μοτίβο που:
α) Ξεκινά με το Α (^Α)
β) Ακολουθείται από 7 χαρακτήρες (.{7})
γ) Στη συνέχεια περιέχει μια διαδοχή από τουλάχιστον ένα C, οσαδηποτε G και τουλάχιστον ένα C που ακολουθούνται από
δ) Τουλάχιστον ένα Τ
Τότε και μόνο τότε θεωρείται επιτυχής η αναζήτηση και αυτός είναι ο λόγος που το $times είναι ίσο με 1 και όχι με 3. H ταύτιση που έγινε είναι μία δηλαδή, απλώς με τις παρενθέσεις έχουμε διαχωρίσει τα στοιχεία της σε 3 διαφορετικές ομάδες.
Το “πριν” και το “μετα”
Κάτι που δε συμβαίνει πολύ συχνά είναι να θέλουμε να ελέγξουμε τι συμβαίνει πριν και μετά από μια ταύτιση σε μια αναζήτηση μοτίβου. Για το σκοπό αυτό μπορούμε να επιστρατευσουμε τις ειδικές μεταβλητές $` , $& και $'. Αυτές συμβολίζουν αντίστοιχα το μέρος του string που προηγείται του, ταυτίζεται με ή ακολουθεί το μοτίβο μας. Δείτε το παράδειγμα:
#! /usr/bin/perl use warnings; $string="AAGCAACACCGCTACGGCGGCTTTAG"; if ($string =~ /C+G*C+/) { # Pattern Search here print "Before: $`\n"; print "Within: $&\n"; print "After : $'\n"; }
Το αποτέλεσμα είναι ότι το string χωρίζεται σε τρία τμήματα με το μεσαίο $& να αντιστοιχεί στο μοτίβο αναζήτησης και τα $` και $' να επιστρέφουν το τμήμα πριν και μετά την ταύτιση αντίστοιχα.
Με τον ίδιο τρόπο που ως τώρα έχουμε δει αντικαταστάσεις απλών σειρών χαρακτήρων μπορούμε να κάνουμε αντικαταστάσεις μοτίβων. Έστω ότι στο παραπάνω παράδειγμα θέλουμε να αντικαταστήσουμε το μοτίβο που εντοπίσαμε με τρεις αστερίσκους ***. Δεν έχουμε παρά να γράψουμε:
#! /usr/bin/perl use warnings; $string="AAGCAACACCGCTACGGCGGCTTTAG"; $string =~ s/((^A.{7})(C+G*C+)(T*))/***/; #Pattern Substitution print $string,"\n";
Μικτά μοτίβα
Έστω τώρα ότι θέλουμε να αναζητήσουμε ή αντικαταστήσουμε μικτά μοτίβα που να αποτελούνται από μια μεταβλητή και μια απλή σειρά χαρακτήρων. Κάτι τέτοιο δε συμβαίνει πολύ συχνά όμως όταν χρειαστεί, απαιτείται ειδική σύνταξη. Έστω ότι σε μια σειρά χαρακτήρων θέλουμε να αντικαταστήσουμε τη λέξη “the” με “xxx”. Μπορούμε να αποδώσουμε αρχικά τον όρο αντικατάστασης σε μια μεταβλητή και να κάνουμε την αντικατάσταση κατά τα γνωστά:
$search = "the"; $string =~ s/$search/xxx/;
Αν τώρα έχοντας τη μεταβλητή $search ήδη δηλωμένη με την τιμή "the" θελήσουμε να αντικαταστήσουμε τη σειρά χαρακτήρων "there" με "xxx" τότε το:
$string =~ s/$searchre/xxx/;
δεν θα λειτουργήσει καθώς δεν υπάρχει η μεταβλητή $searchre. H παραπάνω εντολή θα οδηγήσει σε σφάλμα.
Αυτό που μπορούμε να κάνουμε είναι να περάσω τη μεταβλητή μέσα σε άγκιστρα και έτσι να την εσωτερικεύσω ώς σειρά χαρακτήρων. Η σειρά των εντολών:
$search = "the"; s/${search}re/xxx/;
κάνει αυτό που θέλαμε με τη χρήση της $search μεταβλητής χωρίς επιπλέον αλλαγές.
Eσωτερικές αναφορές (backreferences)
Mια ακόμα δυνατότητα που προσφέρεται κατά την διαδικασία της αντικατάστασης μοτίβων είναι η χρήση εσωτερικών αναφορών. Οι εσωτερικές αναφορές είναι ουσιαστικά οι μεταβλητές που δημιουργεί η Perl για τις ομάδες των κανονικών εκφράσεων που γίνονται matched και συλλαμβάνονται μέσα σε παρενθέσεις ($1, $2 κλπ). Αυτές μπορούν να χρησιμοποιηθούν στην ίδια τη γραμμή που κάνει την αντικατάσταση τροποποιώντας το string στο οποίο ταυτίστηκαν. Στο παρακάτω παράδειγμα, δείτε τη λειτουργία των $1, $2 και $3:
#! /usr/bin/perl use warnings; $string="AAGCAACACCGCTACGGCGGCTTTAG"; $string =~ s/(^A.{7})(C+G*C+)(T*)/$3 $2 $1/) ; # Pattern Substitution and rearrangement print $string,"\n";
Στην γραμμή της αντικατάστασης η Perl αρχικά εντοπίζει το μοτίβο, στη συνέχεια αποδίδει σε τρεις μεταβλητές τις τρείς ομάδες χαρακτήρων και στη συνέχεια τις αντικαθιστά βάζοντας την τρίτη στην αρχή και την πρώτη στο τέλος, κρατώντας στη θέση της, δεύτερη και βάζοντας ανάμεσά τους κενά.
Με βάση τα παραπάνω σκεφτείτε με ποιον τρόπο θα παίρνατε το αρχείο genetic_code.txt του Practical 03 και θα δημιουργούσατε ένα καινούργιο στο οποίο η πρώτη στήλη θα περιείχε τα αμινοξέα και η δεύτερη τα κωδικόνια.
Σκεφτείτε επίσης τις εντολές με τις οποίες θα γίνει η αντικατάσταση των γραμμών ενός κειμένου με γραμμές στις οποίες ο πρώτος και ο τελευταίος χαρακτήρας θα έχουν αντικατασταθεί.
Πρόκειται για χαρακτήρες που τροποποιούν τη διαδικασία της εύρεσης (και κατά συνέπεια και της αντικατάστασης). Κάποιους τους έχουμε ήδη δει. Οι βασικότεροι είναι:
if (/case/i) →αναγνωρίζει CASE, cAsE, case, κλπ
Θυμηθείτε πως κανονικά όλες οι αναζητήσεις είναι case sensitive. Τα κεφαλαία με τα πεζά δεν είναι το ίδιο.
if (/case /x) → βρίσκει το “case”
Αφήνεται δηλαδή μια σχετική ελευθερία στην ύπαρξη κενών στο μοτίβο.
while (/case/g) → βρίσκει όλα τα “case”
Σημείωση: if αναζητήσεις με g-modifier μπορεί να δουλεύουν αλλά δημιουργούν προβλήματα στα όρια (πρώτη και κυρίως τελευταία εμφάνιση του μοτίβου). Επιπλέον δεν επιτρέπουν επιτάχυνση της διαδικασίας αναζήτησης και εύκολης σύνδεσής της με άλλες εντολές.
Eίναι τυπικά αντίθετο του g. Η αναζήτηση είναι επιτυχής με μια μόνο εύρεση του μοτίβου και σταματά εκεί. Ομοιώς ή εντολή:
$string =~ s/$pattern1/$pattern2/o;
θα αντικατάστήσει μόνο την πρώτη εμφάνιση του $pattern1 με το $pattern2 ακόμα κι αν το $pattern1 βρίσκεται μέσα στην αλληλουχία περισσότερες από μια φορές.
if ($sequence =~ /\$/) → αναζητά το “$”
if ($sequence =~ /\^/) → αναζητά το “^”
if ($sequence =~ /\\/) → αναζητά το “\”
Σκεφτείτε λίγο τι αναζητά η παρακάτω εντολή:
if ($sequence =~ /\$site/)
Διαβάζοντας το παρακάτω (Lookahead)
Η ευελιξία που έχουμε στις ευρέσεις δεν είναι το ίδιο μεγάλη στις αντικαταστάσεις λόγω της φύσης της εντολής της αντικατάστασης οπότε και καλούμαστε να αλλάξουμε ένα μοτίβο με ένα άλλο. Εκτός από τις εσωτερικές αναφορές, που είδαμε παραπάνω υπάρχουν κάποιες αρκετά πιο εκλεπτυσμένες αντικαταστάσεις που μπορούν να γίνουν λαμβάνοντας υπ' όψιν το συνολικότερο "περιβάλλον" context του string. Μπορούμε έτσι να αναζητήσουμε το μοτίβο που επιθυμούμε σε συνάρτηση με τις περιβάλλουσες σειρές χαρακτήρων πρίν και μετά από αυτό. Στο παρακάτω παράδειγμα επιθυμούμε να αντικαταστήσουμε το ΑΑΑ με ΤΤΤ αλλά μόνο αν το ΑΑΑ ακολουθείται από GCT. Μπορούμε να πετύχουμε κάτι τέτοιο με τον τελεστή με τον "τελεστή ερώτησης" (?= ...)
$sequence =~ s/AAA(?=GCT)/TTT/ ;
Η παραπάνω εντολή αναζητά το ΑΑΑ εφόσον ακολουθείται από GCT και μόνο τότε αντικαθιστά το AAA με ΤΤΤ. Προσέξτε ότι η παρένθεση σε αυτή τη σύνταξη δεν αποτελεί "σύλληψη" μοτίβου αλλά περιλαμβάνει τον λογικό έλεγχο "ακολουθείται το ΑΑΑ από GCT;"
Ο αντίθετος λογικός έλεγχος, ό έλεγχος άρνησης δηλαδή συντάσσεται με τον τελεστή άρνησης "!" αντί του τελεστή ταύτισης "=". Θα γράψουμε δηλαδή:
$sequence =~ s/ΑΑΑ(?!GCT)/TTT/ ;
κι έτσι η αντικατάσταση γίνεται ΜΟΝΟ εκεί που το ΑΑΑ ΔΕΝ ακολουθείται απο GCT.
Διαβάζοντας το προηγούμενο (lookbehind)
Αντίστοιχες αναζητήσεις σε context με προηγούμενους χαρακτήρες συντάσσονται με τον τελεστή "ερώτηση προς τα πίσω" (?<= ...)
$sequence =~ s/(?<=GCT)ΑΑΑ/TTT/ ;
Και αρνητικά
$sequence =~ s/(?<!GCT)ΑΑΑ/TTT/ ;
Πολύ συχνά μας ενδιαφέρει όχι μόνο η ύπαρξη ή όχι ενός μοτίβου σε ένα string ή ο αριθμός των εμφανίσεών του αλλά και οι θέσεις που αυτό εντοπίζεται. Μια πολύ χρήσιμη συνάρτηση γι' αυτό το σκοπό είναι η pos. H χρήση της είναι απλή και συντάσσεται με ένα while βρόχο επανάληψης σε σύνδεσης με τον g-modifier
while ($string =~ /$pattern/g) { $site=pos($string); print $site,"\n"; }
Η αναζήτηση συνδέεται με την επίκληση της συνάρτησης pos() πάνω στον στόχο της που είναι το $string. Η συνάρτηση επιστρέφει έναν ακέραιο που αντιστοιχεί στην κατάταξη του πρώτου χαρακτήρα αμέσως μετά την τελευταία εύρεση του μοτίβου.
A. Mε ποιον τρόπο θα παίρνατε το αρχείο genetic_code.txt του Practical 03 και θα δημιουργούσατε ένα καινούργιο στο οποίο η πρώτη στήλη θα περιείχε τα αμινοξέα και η δεύτερη τα κωδικόνια.
B. Σκεφτείτε επίσης τις εντολές με τις οποίες θα γίνει η αντικατάσταση των γραμμών ενός κειμένου με γραμμές στις οποίες ο πρώτος και ο τελευταίος χαρακτήρας θα έχουν αμοιβαία αντικατασταθεί.
1. Να διαβάσετε ένα αρχείο multifasta σε μια μεταβλητή hash κρατώντας σαν κλειδί τον τίτλο της κάθε αλληλουχίας και σαν τιμή την αλληλουχία (δοκιμάστε το στο αρχείο multifa.fa)
2. Nα εντοπίσετε όχι μόνο την ύπαρξη αλλά και τις θέσεις του κάθε μοτίβου στην Άσκηση 2.
3. Nα αναζητήσετε στις αλληλουχίες του multifa.fa την ύπαρξη μοτίβων Lys, Αla τα οποία ορίζονται ως ΚΚ-0,3 αμινοξέα-ΑΑ.
4. Nα αναζητήσετε τα μοτίβα δράσης περιοριστικών ενδονουκλεασών που θα βρείτε στο αρχείο restriction.txt μέσα στο γονιδίωμα του E.coli (ecoli.fa στην Άσκηση 4)
Aς δούμε κάποια χαρακτηριστικά του τρόπου με τον η Perl κάνει τις αναζητήσεις για να καταλάβουμε και καλύτερα τον τρόπο με τον οποίον θα τις χειριστούμε. Φανταστείτε το παρακάτω πρόγραμμα:
#!/usr/bin/perl # matchtest.pl use warnings; $sentence="1: A silly sentence (495,a) *BUT* one which will be useful. (3)"; print "Enter a regular expression:"; $pattern=<STDIN>; chomp($pattern); if ($sentence =~ /$pattern/) { print "First pattern was: '", $1,"'\n"; } exit;
Στη συνέχεια θα εκτελέσουμε το παρακάτω πρόγραμμα με διαφορετικές αναζητήσεις για να δούμε αναλυτικά κάποιες ειδικές περιπτώσεις ερμηνείας των κανόνων pattern matching της Perl.
Εκτελούμε το πρόγραμμα και στο πεδίο που μας ζητείται regular expression γράφουμε:
Enter a regular expression: ([a-z]+)
Ζητάμε δηλαδή να μας επιστραφεί μια σειρά από τουλάχιστον 1 πεζά γράμματα. Το αποτέλεσμα θα είναι:
First pattern was: 'silly'
Από αυτό προκύπτει ότι:
1. Η αναζήτηση θα γίνει έτσι ώστε να αποκομιστεί το μεγαλύτερο δυνατό μοτίβο.
2. Η αναζήτηση έχει μεγάλη προτεραιότητα. Το μοτίβο θα είναι αυτό που βρίσκεται το νωρίτερο δυνατό.
Δοκιμάστε τώρα το παρακάτω παράδειγμα με την εξής, ελαφρώς αλλαγμένη εκδοχή του προγράμματος
#!/usr/bin/perl # matchtest.pl use warnings; $sentence="1: A silly sentence (495,a) *BUT* one which will be useful. (3)"; print "Enter a regular expression:"; $pattern=<STDIN>; chomp($pattern); if ($sentence =~ /$pattern/) { print "First pattern was: '", $1,"'\n"; print "Next pattern was: '", $2,"'\n"; print "And then it was: '", $3,"'\n"; } exit;
Στην αναζήτηση, ζητείστε το παρακάτω:
Enter a regular expression: ([a-z]+)(.+)([a-z]+)
Το αποτέλεσμα που θα δείτε θα είναι το εξής:
First pattern was: 'silly'
Next pattern was: ' sentence (495,a) *BUT* one which will be usefu'
And then it was: 'l'
Από αυτό προκύπτει ότι:
3. Η αναζήτησηση γίνεται "άπληστη". Εφόσον βρει ένα μοτίβο, θα το επεκτείνει για όσο περισσότερο δεν καταστρατηγεί τους κανόνες αναζήτησης.
Αντίθετα αν αναζητήσετε με:
Enter a regular expression: ([a-z]+)(.?)([a-z]+)
Το αποτέλεσμα που θα δείτε θα είναι το εξής:
First pattern was: 'silly'
Next pattern was: ' '
And then it was: 'sentence'
Από αυτό προκύπτει ότι:
4. Η "απληστία" στην αναζήτηση μπορεί να ελεγχθεί με σωστή σύνταξη των regular expressions.
Γενικός Κανόνας
Η αναζήτηση μιας κανονικής έκφρασης ξεκινά το συντομότερο δυνατό, βρίσκει το μεγαλύτερο δυνατό μοτίβο, έπειτα τελειώνει το συντομότερο δυνατό επιλέγοντας ανάμεσα σε διαφορετικά ενδεχόμενα πάντοτε εκείνο που ικανοποιείται νωρίτερα (από αριστέρα προς τα δεξιά).