Γνωρίζεις ήδη τα βασικά: κλάσεις χαρακτήρων όπως \d και [A-Z],
ποσοδείκτες όπως {4} και +, και τις λέξεις matches?,
all-matching-subseqs και re-replace. Αυτή η άσκηση προσθέτει τα κομμάτια
των κανονικών εκφράσεων που περιγράφουν δομή και συμφραζόμενα. Ζουν
στο ίδιο λεξιλόγιο regexp, γραμμένα με το ίδιο literal
R/ /.
Οι άγκυρες ταιριάζουν με μια θέση και όχι με έναν χαρακτήρα:
^ ! the start of the string
$ ! the end of the string
\b ! a word boundary
Επειδή το $ σημαίνει το τέλος της συμβολοσειράς, γράψε \$ όταν εννοείς
ένα κυριολεκτικό σύμβολο δολαρίου.
Ένα όριο λέξης (\b) είναι το σημείο όπου ένας χαρακτήρας λέξης συναντά
έναν χαρακτήρα που δεν ανήκει σε λέξη. Οι χαρακτήρες λέξης είναι γράμματα,
ψηφία και η κάτω παύλα· όλα τα υπόλοιπα (κενά, σημεία στίξης και τα δύο άκρα
της συμβολοσειράς) μετράνε ως όριο. Έτσι, το \bcat\b ταιριάζει με το cat
μόνο όταν στέκεται μόνο του:
USING: regexp ;
"cat nap" R/ \bcat\b/ re-contains? . ! => t
"scatter" R/ \bcat\b/ re-contains? . ! => f (cat sits inside a word)
Οι παρενθέσεις ( ) ομαδοποιούν ένα μέρος ενός μοτίβου, ώστε ένας
ποσοδείκτης, ή ένα | ("αυτό ή εκείνο"), να εφαρμόζεται σε ολόκληρη την
ομάδα:
USING: regexp ;
"abab" R/ (ab)+/ matches? . ! => t (+ applies to "ab")
"dog" R/ cat|dog/ matches? . ! => t (either word)
"$100.50" R/ \$\d+(\.\d{2})?/ matches? . ! => t
"$100" R/ \$\d+(\.\d{2})?/ matches? . ! => t (the group is optional)
Το τελευταίο μοτίβο διαβάζεται ως εξής: "ένα σύμβολο δολαρίου, ένα ή περισσότερα ψηφία και μετά μια προαιρετική ομάδα από μια τελεία και δύο ψηφία".
Σημείωση: στη Factor μια ομάδα απλώς δομεί το μοτίβο. Σε αντίθεση με κάποια εργαλεία κανονικών εκφράσεων, δεν μπορείς να ανακτήσεις το κείμενο που ταίριαξε μια ομάδα: δεν υπάρχει εξαγωγή ομάδας σύλληψης και δεν υποστηρίζονται back-references όπως το
\1. Για να ξεχωρίσεις κείμενο γύρω από ένα ορόσημο, χρησιμοποίησε lookaround (παρακάτω).
Ένα lookaround ελέγχει τι έρχεται ακριβώς πριν ή ακριβώς μετά το τρέχον σημείο χωρίς να κάνει αυτούς τους χαρακτήρες μέρος του ταιριάσματος. Είναι "μηδενικού πλάτους": αυτό που ελέγχει δεν συμπεριλαμβάνεται στο αποτέλεσμα.
(?=...) ! lookahead: ... must follow
(?!...) ! negative lookahead: ... must NOT follow
(?<=...) ! lookbehind: ... must come before
(?<!...) ! negative lookbehind: ... must NOT come before
Το lookaround είναι επιλεκτικό: οι ίδιοι χαρακτήρες ταιριάζουν ή όχι
ανάλογα με το τι βρίσκεται δίπλα τους. Ένα σκέτο \d+ αρπάζει κάθε αριθμό·
το lookbehind και το lookahead κρατούν μόνο όσους έχουν τον σωστό γείτονα:
USING: regexp ;
"3 for $10 or 10 for $30" R/ \d+/ all-matching-subseqs .
! => { "3" "10" "10" "30" } (every number)
"3 for $10 or 10 for $30" R/ (?<=\$)\d+/ all-matching-subseqs .
! => { "10" "30" } (only the prices — the bare 3 and 10 drop)
"buy 2 at 15% off, 4 at 30% off" R/ \d+(?=%)/ all-matching-subseqs .
! => { "15" "30" } (only the discounts — the bare 2 and 4 drop)
Πρόσθεσε ένα γράμμα μετά το / κλεισίματος για να αλλάξεις τη συμπεριφορά
ολόκληρου του μοτίβου. Το i το κάνει να αγνοεί τη διάκριση πεζών-κεφαλαίων:
USING: regexp ;
"REFUND" R/ refund/i matches? . ! => t
"Refund" R/ refund/i matches? . ! => t
Το all-matching-subseqs σου δίνει το κείμενο που ταίριαξε. Το map-matches
πάει ένα βήμα παραπέρα: εκτελεί ένα quotation σε κάθε ταίριασμα και
συλλέγει τα αποτελέσματα. Το quotation λαμβάνει τη start θέση του
ταιριάσματος, την end θέση του και ολόκληρη τη συμβολοσειρά· το subseq
μετατρέπει αυτά τα τρία στο κείμενο που ταίριαξε, το οποίο μετά
μετασχηματίζεις:
map-matches ( string regexp quot: ( start end string -- obj ) -- seq )
USING: math regexp sequences ;
"a1 b22 c333" R/ \d+/ [ subseq length ] map-matches .
! => { 1 2 3 } (the length of each run of digits)
Ο ξάδελφός του που εκτελεί μόνο παρενέργειες, το each-match,
εκτελεί ένα quotation σε κάθε ταίριασμα χωρίς να συλλέγει τίποτα.
Κρατάς τα βιβλία μιας μικρής εταιρείας και οι συναλλαγές της κάθε μέρας φτάνουν ως ακατάστατες γραμμές κειμένου. Θα χρησιμοποιήσεις κανονικές εκφράσεις για να επικυρώσεις ποσά, να βγάλεις νούμερα με βάση το σύμβολο που τα σημαδεύει και να επισημάνεις γραμμές που χρειάζονται πιο προσεκτική ματιά.
Ένα σωστά διαμορφωμένο ποσό αποτελείται από ένα $, ένα ή περισσότερα ψηφία και ένα προαιρετικό τμήμα λεπτών, δηλαδή μια τελεία ακολουθούμενη από ακριβώς δύο ψηφία: $100 ή $100.50, αλλά όχι $100.5. Όρισε τη valid-amount?, που επιστρέφει t όταν ολόκληρη η συμβολοσειρά είναι ένα σωστά διαμορφωμένο ποσό.
"$100.50" valid-amount? . ! => t
"$100" valid-amount? . ! => t
"$100.5" valid-amount? . ! => f
Όρισε τη dollar-amounts, που επιστρέφει κάθε αριθμό που ακολουθεί αμέσως μετά από ένα $ στη γραμμή, μόνο τα ψηφία, χωρίς το $, με τη σειρά.
"spent $100 and $25 today" dollar-amounts .
! => { "100" "25" }
Όρισε τη percentages, που επιστρέφει κάθε αριθμό που ακολουθείται αμέσως από ένα %.
"up 5% then down 12%" percentages .
! => { "5" "12" }
Όρισε τη flagged?, που επιστρέφει t όταν η γραμμή αναφέρει refund ή chargeback με οποιονδήποτε συνδυασμό κεφαλαίων και πεζών γραμμάτων.
"Issued a REFUND today" flagged? . ! => t
"a normal sale" flagged? . ! => f
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Factor με 47 έννοιες163 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.