User Tools

Site Tools


java:regular_expr

Regular Expressions

Τα regular expressions είναι αλφαριθμητικά μέσω των οποίων μπορούμε να περιγράψουμε πρότυπα ταιριάσματος μέσα σε μία αλληλουχία χαρακτήρων (string). Τα regular-expressions αποτελούν ειδική γλώσσα η οποία προτυποποιήθηκε από τον Αμερικανό μαθηματικό Stephen Cole Kleene. Υλοποιήσεις regular-expressions μπορείτε να βρείτε σε όλες τις σύγχρονες γλώσσες προγραμματισμού.

Οι κλάσης java.util.regex.Pattern & java.util.regex.Matcher

Η Java υλοποιεί τα regular-expressions μέσω του πακέτου java.util.regex. Το πακέτο αυτό διαθέτει τις εξής δύο κλάσεις:

  • Pattern: Η κλάση Pattern κατασκευάζει ένα regular-expression pattern (πρότυπο ταιριάσματος), το οποίο στη συνέχεια θα χρησιμοποιηθεί από ένα αντικείμενο τύπου Matcher προκειμένου να γίνει το ταίριασμα.
  • Matcher: Η κλάση Matcher υλοποιεί τα διαδοχικά ταιριάσματα ενός pattern σε ένα αλφαριθμητικό.

Η μορφή ενός προγράμματος που χρησιμοποιεί τις παραπάνω κλάσεις είναι η εξής:

  Pattern p = Pattern.compile("a+b");
  Matcher m = p.matcher("$aabhelloabworldaab*");
  boolean b = m.matches();
  while(m.find()) {
    String str = m.group();        // get the next matching pattern
    int startIndex = m.start();    // get the starting index of the pattern
    int endIndex = m.end();        // get the ending index of the pattern
    System.out.print("Next matching pattern is: "+str);
    System.out.print(", starts at index: "+startIndex);
    System.out.println(" and ends at index: "+endIndex);
  }

Το παραπάνω πρόγραμμα ορίζει το pattern ένα ή περισσότερα a ακολουθούμενα από ένα b. Τα ταιριάσματα μέσα στο string aabhelloabworldaab είναι τα εξής:

  • aabhelloabworldaab
  • aabhelloabworldaab
  • aabhelloabworldaab

Η κλάση Matcher έχει τις εξής βασικές μεθόδους:

  • Η μέθοδος find όταν καλείται επιστρέφει (true/false) κατά πόσο βρέθηκε το ζητούμενο πρότυπο (pattern) μέσα στο αλφαριθμητικό. Η συνάρτηση μπορεί να κληθεί έως ότου να επιστρέψει false ώστε να εντοπιστούν όλες οι εμφανίσεις του προτύπου στο αλφαριθμητικό. Όταν η κλήση της συνάρτησης επιστρέφει false σηματοδοτείται ότι δεν υπάρχουν άλλα πρότυπα προς εντοπισμό.
  • Η μέθοδος group επιστρέφει το τμήμα του αλφαριθμητικού που ταιριάζει με το πρότυπο για την τρέχουσα επιτυχή σύγκριση (εμφάνιση).
  • Οι μέθοδοι start και end επιστρέφουν τις θέσεις της αρχής και του τέλους της τρέχουσας εμφάνισης του προτύπου μέσα στο αλφαριθμητικό.

Παράδειγμα

Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "I allocated room for my cat";
 
    // βρες όλες τις αλληλουχίες χαρακτήρων cat μέσα στο αλφαριθμητικό.
    Pattern p = Pattern.compile("cat");  
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println(input.substring(0, m.start())+ " (pos: "+m.start()+","+m.end()+") " + input.substring(m.end()));
    }
  }
}

Πρότυπα ταιριάσματος

Με βάση το πρότυπο των regular-expressions η Java ορίζει τα παρακάτω μοτίβα ταιριάσματος.

Ταιριάσματα χαρακτήρων

Μοναδικοί χαρακτήρες

a 	Ο χαρακτήρας a (αφορά όλους τους μη ειδικούς χαρακτήρες)
\\ 	Ο χαρακτήρας backslash (escaped by an extra \ character)
\t 	Ο χαρακτήρας tab ('\u0009')
\n 	Ο χαρακτήρας newline (line feed) ('\u000A')
\r 	Ο χαρακτήρας carriage-return ('\u000D')
\f 	Ο χαρακτήρας form-feed ('\u000C')
\xhh 	Ο χαρακτήρας 0xhh
\uhhhh 	Ο χαρακτήρας 0xhhhh
Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "I allocated \\ room \nfor \t my cat\\";
    System.out.println("Input String: "+ input+"\n-------------");
 
    // βρες όλους τους χαρακτήρες TAB ή χαρακτήρες αλλαγής γραμμής \n
    // ή χαρακτήρες \ (backslash)
    Pattern p = Pattern.compile("\n|\t|\\\\"); // definition of \ is \\\\
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println(input.substring(0, m.start())+ " (pos: "+m.start()+","+m.end()+") " + input.substring(m.end()));
    }
  }
}

Ομάδες χαρακτήρων

[abc] 	        Οι χαρακτήρες a ή b ή c 
[^abc] 	        Οποιοσδήποτε χαρακτήρας εκτός των a, b, c (άρνηση)
[a-zA-Z] 	a έως z ή A έως Z
[a-d[m-p]] 	a έως d ή m έως p: [a-dm-p] (ένωση δύο συνόλων χαρακτήρων)
[a-z&&[def]] 	d, e, ή f (τομή δύο συνόλων)
[a-z&&[^bc]] 	a έως z, εκτός από τους χαρακτήρες b και c (διαφορετικά [ad-z]) (αφαίρεση συνόλου χαρακτήρων από ένα άλλο σύνολο χαρακτήρων).
[a-z&&[^m-p]] 	a έως z, αλλά όχι m έως p (διαφορετικά [a-lq-z]) (αφαίρεση συνόλου χαρακτήρων από ένα άλλο σύνολο χαρακτήρων).
Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "I allocated room \nfor \t my cat";
 
    Pattern p = Pattern.compile("[A-Za-z&&[^lmt]]");
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println("Found: "+input.substring(m.start(), m.end()) );
    }
  }
}
Συντομογραφίες ομάδων χαρακτήρων
. 	Οποιοσδήποτε χαρακτήρας (may or may not match line terminators)
\d 	ψηφίο: [0-9]
\D 	Μη ψηφίο: [^0-9]
\h 	Οριζόντιος κενός χαρακτήρας (εξαιρούνται χαρακτήρες αλλαγής γραμμής): [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]
\H 	Μη οριζόντιος κενός χαρακτήρας: [^\h]
\s 	Κενός χαρακτήρας: [ \t\n\x0B\f\r]
\S 	Μη κενός χαρακτήρας: [^\s]
\v 	Χαρακτήρας αλλαγής γραμμής: [\n\x0B\f\r\x85\u2028\u2029]
\V 	Εξαιρούνται χαρακτήρες αλλαγής γραμμής: [^\v]
\w 	Χαρακτήρας λέξης: [a-zA-Z_0-9]
\W 	Μη χαρακτήρας λέξης: [^\w]
Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "Ι ate 10\tpieces\tof\tdark\tchocolate.";
 
    // βρες όλα τα ψηφία που ακολουθούνται από κενούς χαρακτήρες
    Pattern p = Pattern.compile("\\d\\s");
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println("Found: "+input.substring(m.start(), m.end()) );
    }
  }
}
Συντομογραφίες POSIX
\p{Lower} 	A lower-case alphabetic character: [a-z]
\p{Upper} 	An upper-case alphabetic character:[A-Z]
\p{ASCII} 	All ASCII:[\x00-\x7F]
\p{Alpha} 	An alphabetic character:[\p{Lower}\p{Upper}]
\p{Digit} 	A decimal digit: [0-9]
\p{Alnum} 	An alphanumeric character:[\p{Alpha}\p{Digit}]
\p{Punct} 	Punctuation: One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
\p{Graph} 	A visible character: [\p{Alnum}\p{Punct}]
\p{Print} 	A printable character: [\p{Graph}\x20]
\p{Blank} 	A space or a tab: [ \t]
\p{Cntrl} 	A control character: [\x00-\x1F\x7F]
\p{XDigit} 	A hexadecimal digit: [0-9a-fA-F]
\p{Space} 	A whitespace character: [ \t\n\x0B\f\r]
Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "- How are you today?\n - Fine! Thank you.\n - You 're welcome.";
 
    // βρες όλα τα σημεία στίξης !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
    Pattern p = Pattern.compile("\\p{Punct}");
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println("Found: "+input.substring(m.start(), m.end()) );
    }
  }
}

Χαρακτήρες προσιορισμού ορίων

^ 	Προσδιορίζει την αρχή της γραμμής
$ 	Προσδιορίζει το τέλος της γραμμής
\b 	(word boundary) Προσδιορίζει τον χαρακτήρα πριν από την αρχή ή το τέλος μίας λέξης
\B 	A non-word boundary
Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "- How are you today?\n- Fine! Thank you.\n- You 're welcome.";
 
    // Βρες τα σημεία στίξης στην αρχή και το τέλος του δοθέντως αλφαριθμητικού
    Pattern p = Pattern.compile("^\\p{Punct}|\\p{Punct}$");
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println("Found: "+input.substring(m.start(), m.end()) +", at: "+m.start() );
    }
  }
}

Μετρητές επαναλήψεων

Σε ένα πρότυπο συχνά θέλουμε να εκφράσουμε ότι ένα συγκεκριμένο πρότυπο ή τμήμα του συνολικού προτύπου εμφανίζεται μία ή περισσότερες φορές. Για παράδειγμα, θέλουμε να βρούμε όλες τις ακολουθίες χαρακτήρων όπου εμφανίζεται ο χαρακτήρας 'a' περισσότερες από μία διαδοχικές φορές. Δείτε το παρακάτω παράδειγμα.

Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "Search in google and arrange meetings in doodle";
 
    // Βρες αλφαριθμητικά που αρχίζουν από g ή d ακολουθούν δύο χαρακτήρες o και τελειώνουν σε g ή d.
    Pattern p = Pattern.compile("[g|d]o{2}[g|d]");
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println("Found: "+input.substring(m.start(), m.end()) +", at: "+m.start() );
    }
  }
}

Greedy quantifiers

X? 	X, once or not at all
X* 	X, zero or more times
X+ 	X, one or more times
X{n} 	X, exactly n times
X{n,} 	X, at least n times
X{n,m} 	X, at least n but not more than m times

Reluctant quantifiers

X?? 	X, once or not at all
X*? 	X, zero or more times
X+? 	X, one or more times
X{n}? 	X, exactly n times
X{n,}? 	X, at least n times
X{n,m}? 	X, at least n but not more than m times

Διάκριση μεταξύ greedy quantifiers και reluctant quantifiers

Όταν χρησιμοποιούνται greedy quantifiers η μηχανή εντοπισμού των regular expressions προσπαθεί να ταιριάξει με βάση το πρότυπο ένα αλφαριθμητικό με όσο το δυνατόν μεγαλύτερο μήκος. Αντίθετα, όταν χρησιμοποιούνται reluctant quantifiers η μηχανή εντοπισμού επιλέγει να ταιριάξει με βάση το πιο πρώτο ταίριασμα που θα συναντήσει.

Για να διαπιστώσετε την αλλαγή στη συμπεριφορά της μηχανής εντοπισμού των regular-expressions, μεταγλωττίστε και εκτελέστε το παρακάτω παράδειγμα με χρήση greedy ή reluctant quantifiers για ένα τμήμα ενός HTML αρχείου. Παρατηρήστε τις αλλαγές στα επιλεγμένα αλφαριθμητικά.

Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "<div> <img src=\"smiley.gif\" alt=\"Smiley face\" height=\"42\" width=\"42\"/> </div><div><h2>Smile</h2></div>";
 
    // Βρες αλφαριθμητικά που αρχίζουν από τον χαρακτήρα '<' και τελειώνουν στον χαρακτήρα '>'
    Pattern p = Pattern.compile("<.*>");  // greedy
    //Pattern p = Pattern.compile("<.*?>"); // reluctant
 
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println("Found: "+input.substring(m.start(), m.end()) +", at: "+m.start() );
    }
  }
}

Ειδικοί χαρακτήρες

Τα regular-expressions εισάγουν ειδικούς χαρακτήρες, οι οποίοι αποτελούν meta-χαρακτήρες. Οι χαρακτήρες αυτοί είναι οι εξής:

  • the backslash \,
  • the caret ^,
  • the dollar sign $,
  • the period or dot .,
  • the vertical bar or pipe symbol |,
  • the question mark ?,
  • the asterisk or star *,
  • the plus sign +,
  • the opening parenthesis (,
  • the closing parenthesis ),
  • the opening square bracket [,
  • and the opening curly brace {

Όταν οι παραπάνω χαρακτήρες αναζητούνται (ως απλοί χαρακτήρες) μέσα σε ένα πρότυπο (pattern) θα πρέπει να προστίθεται πριν από αυτούς ο χαρακτήρας '\' (backslash), ώστε να εξαλειφθεί η ιδιότητα του meta-χαρακτήρα και να λογίζονται ως απλοί χαρακτήρες. Δείτε το παρακάτω παράδειγμα.

Regexp.java
import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String input = "Can you locate all ? (question marks), dollar signs($), carrets(^)  or dots(.) in this character sequence";
 
    // Βρες αλφαριθμητικά που αρχίζουν από τον χαρακτήρα '<' και τελειώνουν στον χαρακτήρα '>'
    Pattern p = Pattern.compile("\\?|\\$|\\^|\\.");
    //Pattern p = Pattern.compile("[?$^.]");
    Matcher m = p.matcher(input);
    while(m.find()) {
      System.out.println("Found: "+input.substring(m.start(), m.end()) +", at: "+m.start() );
    }
  }
}

Εναλλακτικά το δοθέν pattern θα μπορούσε να γραφεί όπως στη γραμμή σε σχόλια. Μέσα σε αγκύλες [… ] οι συγκεκριμένοι meta-χαρακτήρες χάνουν τις ιδιότητες τους με εξαίρεση τον χαρακτήρα ^ ο οποίος διατηρεί την ιδιότητα του meta-χαρακτήρα όταν εισάγεται πρώτος. Για το λόγο αυτό ο χαρακτήρας ^ δεν θα πρέπει να εισαχθεί ως πρώτος χαρακτήρας, μπορεί όμως να εισαχθεί ως 2ος, 3ος κ.ο.κ.

Η παρακάτω δήλωση είναι λάθος, καθώς ο χαρακτήρας ^ στην πρώτη θέση είναι ειδικός χαρακτήρας. Το παρακάτω πρότυπο ταιριάζει όλους τους χαρακτήρες εκτός από τους ?$..

Pattern p = Pattern.compile("[^?$.]");

Grouping

Η μηχανή των regular-expressions δίνει τη δυνατότητα να εντοπίζονται υπό-πρότυπα (sub-patterns)μέσα στο συνολικό regular-expressions, τα οποία ονομάζονται groups. Μπορείτε να έχετε πρόσβαση στα συγκεκριμένα υπο-πρότυπα, εφόσον ορίσετε το καθένα από αυτά τα υποπρότυπα μέσα σε παρενθέσεις.

Η αρίθμηση των groups γίνεται μετρώντας τις παρενθέσεις που ανοίγουν από τα αριστερά προς τα δεξιά. Το group(0) είναι πάντοτε το συνολικό (γενικό) regular expression match. Για παράδειγμα στην έκφραση (A)(B(C))υπάρχουν τα παρακάτω τέσσερα (4) groups.

    0     	(A)(B(C))
    1     	(A)
    2     	(B(C))
    3     	(C)

Δείτε το παρακάτω παράδειγμα, όπου ορίζονται τα δύο subgroups (\\p{Alpha}+ing) και (\\p{Alpha}+ar).

import java.util.regex.*;
 
public class Regexp {
  public static void main(String []args) {
    String str = "Learning irregular verbs is much harder than learning regular verbs";
    Pattern p = Pattern.compile("(\\p{Alpha}+ing)\\s(\\p{Alpha}+ar)");
    Matcher m = p.matcher(str);
    while(m.find()) {
      System.out.println("group(0): "+m.group(0));
      System.out.println("group(1): "+m.group(1));
      System.out.println("group(2): "+m.group(2));
    }
  }
}

Το παρακάτω παράδειγμα εκτυπώνει τα εξής;

group(0): Learning irregular
group(1): Learning irregular
group(2): Learning
group(3): irregular
group(0): learning regular
group(1): learning regular
group(2): learning
group(3): regular
java/regular_expr.txt · Last modified: 2021/03/10 18:32 (external edit)