Tuesday, December 30, 2008

Java Regular Expressions



Huehuehue ... kalo denger regex(regular expression) jadi inget sama matakuliah teori bahasa dan automata yang bahas2 regular expression dan berkutat pada NDFA, DFA, NFA, PDA, !@#^$#*&^!*&@%*@^# gak ngertilah pokoknya cukup bikin kita pusing, yah lupain dulu deh masalah kuliah ... now lets focus to java regex. Regular expression tak lain adalah cara untuk mendeskripsikan suatu set string berdasarkan karakteristik tertentu. Regex dapat dimanfaatkan untuk melakukan search, edit atau manipulasi text/data. Regex di java hampir mirip dengan regex di perl tidak hanya java/perl hampir semua bahasa mempunyai regex untuk pengolahan text/data. Jadi apapun bahasanya minumnya tetep teh botol sossro.
Java regex sebenarnya cuman berkutat pada package java.util.regex yang mempunyai tiga class yaitu Pattern, Matcher dan PatternSintaxException. Object Pattern mempunyai dua method utama untuk melakukan manipulsi text/data yaitu compile dan matcher, compile digunakan untuk menerima masukan pattern dari regex sedangkan matcher digunakan untuk menerima masukan berupa string yang akan dicocokkan berdasarkan karakteristik regex yang dimasukkan pada method compile.

contoh penggunaan Class Pattern pada java, kode berikut akan digunakan untuk percobaan-percobaan regex pada tulisan ini :
/*
* Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Sun Microsystems nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import java.io.Console;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class RegexTestHarness {

public static void main(String[] args){
Console console = System.console();
if (console == null) {
System.err.println("No console.");
System.exit(1);
}
while (true) {

Pattern pattern =
Pattern.compile(console.readLine("%nEnter your regex: "));

Matcher matcher =
pattern.matcher(console.readLine("Enter input string to search: "));

boolean found = false;
while (matcher.find()) {
console.format("I found the text \"%s\" starting at " +
"index %d and ending at index %d.%n",
matcher.group(), matcher.start(), matcher.end());
found = true;
}
if(!found){
console.format("No match found.%n");
}
}
}
}

Setelah compile dan jalankan program untuk melakukan test regex pada posting ini.
amru@bl4ckcub3:~/MyJava/learn/regex$ javac RegexTestHarness.java

jalankan program
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex:


jika program dijalankan akan muncul promt yang meminta kita untuk memasukkan regex. Contoh sederhana dalam menggunakan program adalah untuk mencari kata dalam sebuah kalimat sebagai berikut :
Enter your regex: foo
Enter input string to search: foo bukanlah bar
I found the text "foo" starting at index 0 and ending at index 3.

Enter your regex:


Dari spesifikasi class Pattern dapat rangkum dalam tabel berikut regular expression yang digunakan pada java.

Character Classes
[abc] a, b, atau c (simple class)
[^abc] Semua karakter a, b, atau c (negation)
[a-zA-Z] a sampai z, atau A sampai Z, inclusive (range)
[a-d[m-p]] a sampai d, atau m sampai p: [a-dm-p] (union)
[a-z&&[def]] d, e, atau f (intersection)
[a-z&&[^bc]] a sampai z, kecuali untuk b dan c: [ad-z] (subtraction)
[a-z&&[^m-p]] a sampai z, dan tidak m sampai p: [a-lq-z] (subtraction)


Berikut adalah penjelasan dari table diatas pada applikasinya :
Simple class digunakan untuk melakukan pencocokan satu persatu dari karakter yang ada didalam tandakurung, misal [bcr]at, maka regex tersebut akan cocok dengan pattern berikut "bat", "cat" dan "rat". Dapat dilihat bahwa regex tersebut akan menerima semua urutan karakter dengan awalan karakter "b","c" dan "r" serta diakhiri dengan pattern "at".
ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: [bcr]at
Enter input string to search: bat
I found the text "bat" starting at index 0 and ending at index 3.

Enter your regex: [bcr]at
Enter input string to search: cat
I found the text "cat" starting at index 0 and ending at index 3.

Enter your regex: [bcr]at
Enter input string to search: rat
I found the text "rat" starting at index 0 and ending at index 3.

Negation
Digunakan untuk mencocokkan semua karakter kecuali yang ada didalam tandakurung ditandai dengan "^", misal [^bcr]at akan mencari pattern untuk semua karakter kecuali yang diawali dengan karakter "b", "c" dan "r".
ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: [^bcr]at
Enter input string to search: bat
No match found.

Enter your regex: [^bcr]at
Enter input string to search: cat
No match found.

Enter your regex: [^bcr]at
Enter input string to search: rat
No match found.

Enter your regex: [^bcr]at
Enter input string to search: hat
I found the text "hat" starting at index 0 and ending at index 3.


Ranges
Untuk menampilkan range pada suatu pattern tertentu ditandai dengan "-". Misal untuk mengenali pattern "a" sampai "c" maka bisa kita gunakan expresi sebagai berikut [a-b].
ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: [a-c]
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.

Enter your regex: [a-c]
Enter input string to search: b
I found the text "b" starting at index 0 and ending at index 1.

Enter your regex: [a-c]
Enter input string to search: c
I found the text "c" starting at index 0 and ending at index 1.

Enter your regex: [a-c]
Enter input string to search: d
No match found.

Enter your regex: foo[1-5]
Enter input string to search: foo1
I found the text "foo1" starting at index 0 and ending at index 4.

Enter your regex: foo[1-5]
Enter input string to search: foo5
I found the text "foo5" starting at index 0 and ending at index 4.

Enter your regex: foo[1-5]
Enter input string to search: foo6
No match found.

Enter your regex: foo[^1-5]
Enter input string to search: foo1
No match found.

Enter your regex: foo[^1-5]
Enter input string to search: foo6
I found the text "foo6" starting at index 0 and ending at index 4.

Union
Misalnya kita akan melakukan pengenalan pattern gabungan antara 0 sampai 4 dan 6 sampai 8 bisa kita lakukan dengan cara [0-4[6-8]], maka selain dari union/gabungan range antara 0 sampai 4 dan 6 sampai 8 tidak akan dikenali.
Ex:
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: [0-4[6-8]]
Enter input string to search: 0
I found the text "0" starting at index 0 and ending at index 1.

Enter your regex: [0-4[6-8]]
Enter input string to search: 5
No match found.

Enter your regex: [0-4[6-8]]
Enter input string to search: 6
I found the text "6" starting at index 0 and ending at index 1.

Enter your regex: [0-4[6-8]]
Enter input string to search: 8
I found the text "8" starting at index 0 and ending at index 1.

Enter your regex: [0-4[6-8]]
Enter input string to search: 9
No match found.


Intersection
Digunakan untuk melakukan pengenalan pattern yang berupa irisan atau ingin mengenali suatu pattern pada range tertentu, bisa menggunakan key &&. Misal ingin mengenali pattern 3, 4 dan 5 pada range 0 sampai 9 maka bisa kita kontruksi regex sebagai berikut [0-9&&[345]]
Ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: [0-9&&[345]]
Enter input string to search: 3
I found the text "3" starting at index 0 and ending at index 1.

Enter your regex: [0-9&&[345]]
Enter input string to search: 4
I found the text "4" starting at index 0 and ending at index 1.

Enter your regex: [0-9&&[345]]
Enter input string to search: 5
I found the text "5" starting at index 0 and ending at index 1.

Enter your regex: [0-9&&[345]]
Enter input string to search: 2
No match found.

Enter your regex: [0-9&&[345]]
Enter input string to search: 6
No match found.

Enter your regex: [2-8&&[4-6]]
Enter input string to search: 3
No match found.

Enter your regex: [2-8&&[4-6]]
Enter input string to search: 4
I found the text "4" starting at index 0 and ending at index 1.

Enter your regex: [2-8&&[4-6]]
Enter input string to search: 5
I found the text "5" starting at index 0 and ending at index 1.

Enter your regex: [2-8&&[4-6]]
Enter input string to search: 6
I found the text "6" starting at index 0 and ending at index 1.

Enter your regex: [2-8&&[4-6]]
Enter input string to search: 7
No match found.


Substraction
Digunakan untuk melakukan pengenalan pada negasi yang berada pada karakter yang bersarang, misal [0-9&&[^345]] berarti melakukan pencarian pada range 0 sampai 9 kecuali 3, 4 dan 5.

Oke lanjut, setelah kita melakukan percobaan pada class2 karakter ada baiknya jika kita kenali juga predifined charakter class, class Pattern telah mendefinisikan pattern2 diatas sehingga kita bisa memanfaatkannya sebagai shorthands.
Predefined Character Classes
. cocok dengan karakter apapun (may or may not match line terminators)
\d digit: [0-9]
\D non digit: [^0-9]
\s karakter whitespace: [ \t\n\x0B\f\r]
\S karakter non-whitespace: [^\s]
\w karakter kata: [a-zA-Z_0-9]
\W karakter non-word: [^\w]


ex:
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: .
Enter input string to search: @
I found the text "@" starting at index 0 and ending at index 1.

Enter your regex: .
Enter input string to search: 1
I found the text "1" starting at index 0 and ending at index 1.

Enter your regex: .
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.

Enter your regex: \d
Enter input string to search: 1
I found the text "1" starting at index 0 and ending at index 1.

Enter your regex: \d
Enter input string to search: a
No match found.

Enter your regex: \D
Enter input string to search: 1
No match found.

Enter your regex: \D
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.

Enter your regex: \s
Enter input string to search:
I found the text " " starting at index 0 and ending at index 1.

Enter your regex: \s
Enter input string to search: a
No match found.

Enter your regex: \S
Enter input string to search:
No match found.

Enter your regex: \S
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.

Enter your regex: \w
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.

Enter your regex: \w
Enter input string to search: !
No match found.

Enter your regex: \W
Enter input string to search: a
No match found.

Enter your regex: \W
Enter input string to search: !
I found the text "!" starting at index 0 and ending at index 1.


Oke dari predifined class bisa kita simpulkan :
Huruf bukan kapital berarti :
\d cocok untuk semua digit
\s cocok untuk whitespace
\w cocok untuk semua karakter penyusun kata

Sedangkan hurus kapital :

\D cocok untuk non-digits
\S cocok untuk non-spaces
\W cocok untuk semua non karakter penyusun kata

Quantifiers
Dengan menggunakan quantifiers kita bisa melakukan pencocokan berdasarkan berapa kali kita akan melakukan pengenalan pattern pada suatu urutan karakter, spesifikasi class Pattern mempunyai 3 quantifiers yaitu greedy, Reluctant dan Possessive. Berikut adalah tabel quantifiers :

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


Sekarang kita akan melakukan percobaan dengan quantifiers greedy menggunakan huruf "a" dengan diikuti ?, *, + kita akan lihat perbedaannya. Input yang digunakan adalah string kosong "".
ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: a?
Enter input string to search:
I found the text "" starting at index 0 and ending at index 0.

Enter your regex: a*
Enter input string to search:
I found the text "" starting at index 0 and ending at index 0.

Enter your regex: a+
Enter input string to search:
No match found.


Pada contoh diatas untuk a? dan a* akan mengijinkan adanya pattern "", karena pattern "" ada pada input string kosong, awal dari input string, akhir dari input string atau diantara huruf pada input string.

kita lakukan percobaan lagi untuk melihat perbedaan dari ketiganya dengan memberikan input masukan berupa karakter "a":
ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: a?
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.

Enter your regex: a*
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.

Enter your regex: a+
Enter input string to search: a
I found the text "a" starting at index 0 and ending at index 1.


dari hasil diatas dapat kita lihat bahwa pada a? dan a* tetap menemukan pattern "" pada akhir karakter, karena pada index 0 ditempati oleh karakter "a" sedang pattern "" akan dikenali pada index ke 1 dan diakhiri pada index 1 juga.

oke sekarang kita coba lagi bagaimana jika masukannya berupa string "aaaaa" ?
ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: a?
Enter input string to search: aaaaa
I found the text "a" starting at index 0 and ending at index 1.
I found the text "a" starting at index 1 and ending at index 2.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "a" starting at index 3 and ending at index 4.
I found the text "a" starting at index 4 and ending at index 5.
I found the text "" starting at index 5 and ending at index 5.

Enter your regex: a*
Enter input string to search: aaaaa
I found the text "aaaaa" starting at index 0 and ending at index 5.
I found the text "" starting at index 5 and ending at index 5.

Enter your regex: a+
Enter input string to search: aaaaa
I found the text "aaaaa" starting at index 0 and ending at index 5.

Nah sekarang dah katahuan kan bedanya huehuehue ..., tapi kita akan coba sekali lagi untuk memastikannya dengan mengubah string masukan menjadi "ababaaaab".
ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: a?
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "" starting at index 3 and ending at index 3.
I found the text "a" starting at index 4 and ending at index 5.
I found the text "a" starting at index 5 and ending at index 6.
I found the text "a" starting at index 6 and ending at index 7.
I found the text "a" starting at index 7 and ending at index 8.
I found the text "" starting at index 8 and ending at index 8.
I found the text "" starting at index 9 and ending at index 9.

Enter your regex: a*
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "" starting at index 1 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "" starting at index 3 and ending at index 3.
I found the text "aaaa" starting at index 4 and ending at index 8.
I found the text "" starting at index 8 and ending at index 8.
I found the text "" starting at index 9 and ending at index 9.

Enter your regex: a+
Enter input string to search: ababaaaab
I found the text "a" starting at index 0 and ending at index 1.
I found the text "a" starting at index 2 and ending at index 3.
I found the text "aaaa" starting at index 4 and ending at index 8.


oke jadi kesimpulannya begini untuk a? akan melakukan pencocokan karakter satu persatu dan akan menggantikan pattern yang tidak cocok dengan ""(string kosong), a* akan melakukan pencocokan karakter yang sama dengan a tetapi tidak satu persatu karakter tapi jika ada pattern yang sama misalnya "aaaa" akan dianggap sebagai sebuah kesatuan dan aka mengganti pattern yang tidak sama dengan "" dan pada a+ bertindak seperti a* tetapi langsung mengabaikan pattern yang tidak cocok.

lalu bagaimana jika kita ingin mencocokkan pattern karakter dengan jumlah tertentu ??, the answer is use the curly braces {n} oke!!
ex :
Enter your regex: a{3}
Enter input string to search: aa
No match found.

Enter your regex: a{3}
Enter input string to search: aaa
I found the text "aaa" starting at index 0 and ending at index 3.

Enter your regex: a{3}
Enter input string to search: aaaa
I found the text "aaa" starting at index 0 and ending at index 3.


dari contoh diatas dapat dilihat pemanfaatan dari {n}, n adalah jumlah karakter yang akan dicocokkan.Tapi kenapa yang pertama gagal ?? itu karena string yang dicocokkan jumlahnya lebih sedikit jika dibadingkan dengan jumlah regex yang akan dikenali meskipun string inputnya sama yaitu "aa". Nah kita lanjut lagi masih seputar {n}, lets look bellow :
Ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: a{3}
Enter input string to search: aaaaaaaaa
I found the text "aaa" starting at index 0 and ending at index 3.
I found the text "aaa" starting at index 3 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.

Enter your regex: a{3,}
Enter input string to search: aaaaaaaaa
I found the text "aaaaaaaaa" starting at index 0 and ending at index 9.

Enter your regex: a{3,6}
Enter input string to search: aaaaaaaaa
I found the text "aaaaaa" starting at index 0 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.


oke penjelasan untuk output diatas adalah sebagai berikut, untuk yang pertama dengan a{3} akan mengenali 3 kali karena input string "aaaaaaaaa" sejumlah 3 kali pattern a{3}, untuk yang kedua dengan {3,} maksudnya akan mengenali pattern minimal dengan panjang karakter "aaa" dengan pangjang maksimal tak terbatas, untuk yang terakhir dengan {3,6} sebenarnya sama dengan yang kedua hanya bedanya ada batas pengenalan panjang pattern yaitu sepanjang 6 karakter.

Sekarang perbadaan antara regex (dog){3} dengan dog{3} ??, look bellow agains ...
Ex :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: (dog){3}
Enter input string to search: dogdogdogdogdogdog
I found the text "dogdogdog" starting at index 0 and ending at index 9.
I found the text "dogdogdog" starting at index 9 and ending at index 18.

Enter your regex: dog{3}
Enter input string to search: dogdogdogdogdogdog
No match found.

terlihat bahwa yang pertama akan mengenali pattern berupa group karakter yaitu "dog" dengan panjang 3, sedangkan yang kedua hanya akan mengenali "dog" dimana yang dihitung jumlahnya bukan "dog" tetapi karakter "g". Untuk melakukan grouping seperti (dog){3} tetapi tidak mementingkan urutan bisa kita gunakan pattern [x]{n}, contoh :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: [abc]{3}
Enter input string to search: abccabaaaccbbbc
I found the text "abc" starting at index 0 and ending at index 3.
I found the text "cab" starting at index 3 and ending at index 6.
I found the text "aaa" starting at index 6 and ending at index 9.
I found the text "ccb" starting at index 9 and ending at index 12.
I found the text "bbc" starting at index 12 and ending at index 15.


Perbedaan antara greedy, reluctant dan possessive ??, okeh kita lihat saja contoh berikut ini :
amru@bl4ckcub3:~/MyJava/learn/regex$ java RegexTestHarness

Enter your regex: .*foo // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.

proses pada gredy adalah pertama dia akan mengkonsumsi semua input dengan menggunakan (.*) termasuk pattern "foo" yang terakhir, perlu diingat pencocokan pada gredy dimulai dari belakang sampai diketemukan pattern "foo" yang terakhir. Itulah kenapa setelah diketemukan pattern "foo" yang terakhir pencocokan akan berhenti.
proses pada reluctant adalah pertama dia tidak akan mengkonsumsi apapun dari input, pencocokannya akan dilakukan dari depan dengan mencocokkan satu persatu sampai diketemukan pattern "foo", jika diketemukan maka akan menghentikan pencocokan dan memulai kembali pencocokan dari index dimana dia berhenti.
proses pada possessive sama dengan greedy tetapi dia tidak akan mengulang lagi jika tidak diketemukan, pencocokan hanya dilakukan sekali saja.

reference : http://java.sun.com/docs/books/tutorial

2 comments:

  1. wihh asik nih, ada pakar regex... aku ajarin yo mas. buat TA.

    pernah coba pake jawk (mirip awk, tapi versi java)??

    ReplyDelete
  2. @kikyou
    halah... bukan pakar cuman iseng2 nulis... minta tolong mba faiz aja yang lebih pakar huehuehue.
    JAWK belum pernah make tuh, klo awknya sering ...

    ReplyDelete