Διαδρομές
/
R
R
/
Ύλη
/
Κανονικές εκφράσεις
Κα

Κανονικές εκφράσεις σε R

1 άσκηση

Σχετικά με την έννοια Κανονικές εκφράσεις

Οι κανονικές εκφράσεις είναι ένας εξαιρετικά ευέλικτος τρόπος για αντιστοίχιση προτύπων σε συμβολοσειρές, χρησιμοποιώντας μια Γλώσσα Ειδικού Τομέα (DSL) σχεδιασμένη ακριβώς για αυτόν τον σκοπό.

Advanced

Όπως πολλές άλλες γλώσσες προγραμματισμού, η R δεν επιχειρεί να υλοποιήσει τη δική της βιβλιοθήκη κανονικών εκφράσεων. Αντ' αυτού, τυλίγει τη μηχανή κανονικών εκφράσεων ICU. Η ICU είναι παρόμοια με τη δημοφιλή βιβλιοθήκη PCRE2 που χρησιμοποιούν οι περισσότερες νεότερες γλώσσες, αν και ορισμένες οριακές περιπτώσεις αντιμετωπίζονται διαφορετικά.

Πολλές δυνατότητες κανονικών εκφράσεων είναι ενσωματωμένες στη Base R, και μπορεί να δεις παραδείγματα σε παλαιότερο κώδικα.

Για νέο κώδικα, συνιστάται ανεπιφύλακτα η χρήση της βιβλιοθήκης stringr.

Όπως αναφέρθηκε στην έννοια Συμβολοσειρές, το stringr έχει καλή τεκμηρίωση, συνήθως γραμμένη από τον δημιουργό (ή τους δημιουργούς) του πακέτου.

Note

Αυτό το πρόγραμμα σπουδών της R υποθέτει ότι έχεις ήδη εξοικείωση με τη βασική σύνταξη των κανονικών εκφράσεων. Θα επικεντρωθούμε αποκλειστικά σε χαρακτηριστικά ειδικά της R.

Παρακάτω παρατίθενται μερικοί πόροι για να φρεσκάρεις τις γνώσεις σου στις κανονικές εκφράσεις.

Πολλές από τις συναρτήσεις που έχουν ήδη συζητηθεί στην έννοια Strings είναι σχεδιασμένες εξ ορισμού για αναζητήσεις με κανονικές εκφράσεις.

Κρατήσαμε την έννοια Strings σχετικά απλή, κάνοντας αντιστοίχιση μόνο σε κυριολεκτικές συμβολοσειρές, αλλά μπορεί να χρησιμοποιηθεί οποιοδήποτε πρότυπο κανονικής έκφρασης.

Τυπικά, μια κανονική έκφραση δημιουργείται στην R καλώντας τη regex(). Για ευκολία, οι συναρτήσεις του stringr αντιμετωπίζουν οποιαδήποτε συμβολοσειρά προτύπου ως κανονική έκφραση. Στο παρακάτω παράδειγμα, το πρώτο str_replace_all() είναι συντακτική ζάχαρη για τη δεύτερη εκδοχή:

input <- "Some input string"

# "\\s" matches any whitespace character
input |> str_replace_all("\\s", "_")
[1] "Some_input_string"

input |> str_replace_all(regex("\\s"), "_")
[1] "Some_input_string"

Το φύλλο αναφοράς είναι ένας χρήσιμος οδηγός: όλες οι συναρτήσεις με όρισμα pattern (τυπωμένο με κόκκινο) δέχονται κανονική έκφραση, και η σελίδα 2 συνοψίζει τη σύνταξη των κανονικών εκφράσεων.

Σημείωσε ότι η συνάρτηση regex() μπορεί να είναι ακόμα σημαντική αν χρειάζεται να παρακάμψεις τις προεπιλογές, όπως τη διάκριση πεζών-κεφαλαίων ή την τοπική ρύθμιση (locale).

# default is case sensitive
"A Mixed Assortment" |> 
    str_replace_all(regex("[aeiou]", ignore_case = TRUE), "*")
#> [1] "* M*x*d *ss*rtm*nt"

Πολλοί κωδικοί κανονικών εκφράσεων περιλαμβάνουν ένα backslash, όπως το \s που χρησιμοποιήθηκε παραπάνω. Στις κανονικές συμβολοσειρές της R, το \ χρειάζεται διαφυγή με διπλασιασμό, εξ ου και το "\\s".

Τα backslash μπορούν να πληθύνουν και να γίνουν μπερδεμένα, οπότε μπορεί να βοηθήσει η χρήση ακατέργαστων συμβολοσειρών σε σύνθετες περιπτώσεις.

Από προεπιλογή, η σύνταξη για τον ορισμό μιας ακατέργαστης συμβολοσειράς είναι r"( )", αλλά επιτρέπονται και άλλοι διαχωριστές. Επειδή οι παρενθέσεις ( ) και οι αγκύλες [ ] είναι και οι δύο συνηθισμένες στη σύνταξη των κανονικών εκφράσεων (για ομάδες σύλληψης και ομάδες επιλογών), τα άγκιστρα { } μπορεί να είναι καλή επιλογή.

input |> str_replace_all(r"{\s\w}", "__")
#> [1] "Some__nput__tring"

Εντοπισμός αντιστοιχιών

Ακολουθούν μερικές ερωτήσεις και συναρτήσεις που μπορούν να βοηθήσουν να τις απαντήσεις.

Ορισμένα παραδείγματα χρησιμοποιούν το fruit, ένα διάνυσμα με 80 ονόματα φρούτων που περιλαμβάνεται στο stringr για εξάσκηση.

Περιέχει μια συμβολοσειρά κάποια αντιστοιχία; Τη str_detect(), ή τις str_starts(), str_ends() για να αποφύγεις τη χρήση των αγκυρών ^ ή $.

# berries begining with b
fruit |> str_detect("^b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

# alternative syntax
fruit |> str_starts("b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

Να επιστραφούν μόνο οι συμβολοσειρές που περιέχουν αντιστοιχία; Χρησιμοποίησε τη str_subset() για να πάρεις τις συμβολοσειρές, και τη str_which() για να πάρεις τις θέσεις.

fruit |> str_subset("a...e")
[1] "apple"       "blackberry"  "mandarine"   "nectarine"   "pineapple"   "pomegranate" "raspberry"   "salal berry"
> fruit |> str_which("a...e")
[1]  1  7 48 51 62 64 70 73

Πόσες αντιστοιχίες υπάρχουν σε κάθε συμβολοσειρά; Τη str_count()

# count vowels
fruit |> str_count("[aeiou]") |> head(10)
#> [1] 2 3 4 3 3 2 2 3 5 3

Πού βρίσκεται η αντιστοιχία; Τη str_view() για διαδραστική χρήση, και γενικότερα τη str_locate() ή τη str_locate_all().

# str_view only shows matching elements, with match in < > delimiters
fruit |> str_view("a...e")
 [1] │ <apple>
 [7] │ bl<ackbe>rry
[48] │ mand<arine>
[51] │ nect<arine>
[62] │ pine<apple>
[64] │ pomegr<anate>
[70] │ r<aspbe>rry
[73] │ sal<al be>rry

# str_locate returns all elements
fruit |> str_locate("a...e") |> head(10)
      start end
 [1,]     1   5
 [2,]    NA  NA
 [3,]    NA  NA
 [4,]    NA  NA
 [5,]    NA  NA
 [6,]    NA  NA
 [7,]     3   7
 [8,]    NA  NA
 [9,]    NA  NA
[10,]    NA  NA

Συλλήψεις

Συχνά, θέλουμε να μάθουμε τι ταιριάζει. Αυτό επιτυγχάνεται συμπεριλαμβάνοντας ομάδες σύλληψης μέσα σε παρενθέσεις στην κανονική έκφραση.

Είναι δυνατό να αναφερθείς ξανά σε συλλήψεις μέσα στην ίδια κανονική έκφραση, χρησιμοποιώντας \1, \2, κ.λπ. Για παράδειγμα, βρίσκοντας επαναλαμβανόμενα ζεύγη γραμμάτων:

fruit |> str_subset("(..)\\1")
[1] "banana"      "coconut"     "cucumber"    "jujube"      "papaya"      "salal berry"

Με παρόμοια σύνταξη, οι συλλήψεις μπορούν να χρησιμοποιηθούν σε συμβολοσειρές αντικατάστασης:

# double to triple
fruit |> 
  str_subset("(..)\\1") |> 
  str_replace("(..)\\1", "\\1\\1\\1")
[1] "bananana"      "cococonut"     "cucucumber"    "jujujube"      "papapaya"      "salalal berry"

Συγκέντρωσε τις αντιστοιχίες για μετέπειτα επεξεργασία χρησιμοποιώντας τη συνάρτηση str_match() για μόνο την πρώτη αντιστοιχία, ή τη str_match_all() για όλες τις αντιστοιχίες.

Οι ομάδες σύλληψης τείνουν να παράγουν περίπλοκη έξοδο σε οποιαδήποτε γλώσσα. Η προσέγγιση της R (η οποία είναι επίσης διανυσματοποιημένη, ως πρόσθετη πρόκληση) είναι να παράγει έναν πίνακα από τη str_match(), με μία γραμμή ανά συμβολοσειρά εισόδου, τη στήλη 1 να περιέχει ολόκληρη τη συμβολοσειρά που αντιστοιχίστηκε, και τις υπόλοιπες στήλες να περιέχουν τις διάφορες ομάδες σύλληψης.

Η χρήση ονομασμένων ομάδων μπορεί να είναι πλεονέκτημα, καθώς γίνονται ονόματα στηλών στον πίνακα αποτελεσμάτων.

recipe <- "dissolve 25g sugar in 200ml water"
recipe |> str_match("(\\d+g) .* (\\d+ml)")
     [,1]                 [,2]  [,3]   
[1,] "25g sugar in 200ml" "25g" "200ml"

# use named capture groups
recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")
                          wt    vol    
[1,] "25g sugar in 200ml" "25g" "200ml"

Δεν έχουμε πει πολλά για τους πίνακες σε αυτό το πρόγραμμα σπουδών, αν και υπάρχει ένα έγγραφο έννοιας στο οποίο μπορείς να έχεις πρόσβαση. Οι πίνακες δεν ταιριάζουν καλά με το οικοσύστημα του Tidyverse, παρόλο που αποτελούν μέρος της Base R εδώ και πολύ καιρό.

Σε γρήγορη σύνοψη: αντιμετώπισε τους πίνακες σαν διανύσματα με 2 θέσεις, με τη σειρά [rows, cols]. Άφησε όποια από τις δύο θέσεις κενή για να πάρεις ολόκληρη τη γραμμή/στήλη.

Όταν παίρνεις μία μόνο γραμμή r με m[r,] ή μία μόνο στήλη c με m[, c], η τιμή που επιστρέφεται ισοπεδώνεται σε διάνυσμα από προεπιλογή.

> amounts <- recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")

# get all of row 1, as a named vector
> amt1 <- amounts[1,]
> amt1
                                       wt                  vol 
"25g sugar in 200ml"                "25g"              "200ml" 

# get a vector element
amt1["wt"] |> unname()
[1] "25g"

Αναπόφευκτα, η str_match_all() παράγει έξοδο με ακόμα περισσότερη δομή:

  • Το αποτέλεσμα είναι μια λίστα, με ένα στοιχείο ανά συμβολοσειρά εισόδου.
  • Αν υπάρχει μόνο μία συμβολοσειρά εισόδου, κάνε unlist στην έξοδο με ευρετηρίαση [[1]], όπως περιγράφεται στην έννοια Λίστες.
  • Κάθε στοιχείο είναι ένας πίνακας, με στήλες όπως στη match().
  • Κάθε γραμμή στον πίνακα είναι μια επιτυχημένη σύλληψη.
mix <- "Mix 200g of sugar, 180g of flour and 150g of butter"

# raw output
> str_match_all(mix, "(\\d+)g")
[[1]]
     [,1]   [,2] 
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"

# we just want the first list element
> str_match_all(mix, "(\\d+)g")[[1]]
     [,1]   [,2] 
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"

# captures are in column 2, so get that as a vector
> str_match_all(mix, "(\\d+)g")[[1]][,2]
[1] "200" "180" "150"

Φυσικά, οι αντιστοιχίσεις μπορούν να αποτύχουν. Το αποτέλεσμα τότε θα είναι η ειδική τιμή NA. Να είσαι έτοιμος να το ελέγξεις, όπως περιγράφεται στην έννοια Τίποτα.

# failed match
recipe |> str_match("(not here)")
     [,1] [,2]
[1,] NA   NA  
Note

Το R for Data Science σημειώνει:

Η str_match() επιστρέφει έναν πίνακα, οπότε δεν είναι ιδιαίτερα εύκολη στη χρήση.

Με την υποσημείωση:

Κυρίως επειδή δεν συζητάμε ποτέ πίνακες σε αυτό το βιβλίο!

Ένας από τους συγγραφείς του βιβλίου έγραψε επίσης τη βιβλιοθήκη stringr, και επικρίνει έμμεσα τη δική του δουλειά.

Θα έχει ενδιαφέρον να δούμε πώς θα εξελιχθεί η υλοποίηση αυτής της συνάρτησης σε μελλοντικές εκδόσεις.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα

Μάθε την έννοια Κανονικές εκφράσεις