Regular Expression ถูกใช้เป็นเครื่องมือตรวจสอบและแก้ไขข้อความที่ใช้เป็นพื้นฐานสำหรับทุกภาษาโปรแกรมมิ่งเลยล่ะฮะ แต่หลายคนมองว่ามันยากอ่ะ ซึ่งบอกตรงนี้เลยว่า เออ มันยาก ยากจริง แต่ถ้าเข้าใจ concept จะต่อยอดง่ายมากฮะ เพราะเงื่อนไขซ้ำซ้อนแค่ไหน ก็ใช้ concept เดิมทั้งหมดฮะ เรามาดู concept ของมันกัน

Concept ของ REGEX

คำว่า REGEX เป็นคำย่อมาจาก REGular EXpression ฮะ มีหน้าตาเป็น string ที่ต้องใช้ร่วมกันกับ method เพื่อทำงานด้านประมวลผล string ได้ดีมากเลยล่ะฮะ

หลักการของ REGEX ที่สำคัญๆ มีดังนี้ฮะ

  1. แต่ละตำแหน่งใน string ของ REGEX คือการเลือก
  2. อักษรทุกตัวจะมีสังกัด class ของมันเองเสมอ เลือก class ให้ตรงกับที่ต้องการ
  3. เลือกจำนวนของอักษรใน class เดียวกัน โดยใช้ quantifier กี่ตัวก็ระบุไป
  4. ถ้ามีหลายตัวเลือก ก็ใช้ alteration
  5. กำหนดขึ้นต้นหรือลงท้าย ค่อยใช้ anchor
  6. ตัวอักขระพิเศษ ให้เขียนในรูป escape characters
  7. เลือกส่วนไหนของข้อความ ก็ใช้ capture groups

Class

string ประกอบด้วยด้วยตัวอักขระในระบบ ASCII อันนี้เป็นพื้นฐานการเขียนโปรแกรมเนอะ ตัว REGEX ก็ใช้ประโยชน์ตรงนี้มากำหนด class ประมาณนี้ฮะ

  • ตัวเลข จะแทนด้วย "\d" หรือ digit แต่ถ้าจะระบุว่าไม่ใช่ตัวเลข จะเป็น "\D"
  • อักษรภาษาอังกฤษ แทนด้วย "\w" หรือ word แต่กลับกัน จะเป็น "\W"
  • ช่องว่าง แทนด้วย "\s" หรือ space กลับกันจะเป็น "\S"
  • ตัวอะไรก็ได้ แทนด้วย "." จุดนั่นแหละฮะ
  • ขึ้นบรรทัดใหม่ หรือ New line ก็เป็น "\R" มาจาก Return แต่ถ้าจะบอกว่าไม่เอาขึ้นบรรทัดใหม่ จะเป็น "\N"
  • อักขระภาษาอื่น แทนด้วย "\p{ภาษา}" เช่น "\p{Thai}" ข้อมูลเพิ่มเติมอ่านได้จาก regular-expressions.info/unicode ฮะ

แต่ถ้าเราต้องการเลือกบางค่าบางตัว เราจะใช้เครื่องหมาย "[]" เช่น

  • เลือกเฉพาะอักษร A, B, C หรือ D เขียนว่า "[ABCD]"
  • ไม่เลือก A, B, C, D ให้เขียนว่า "[^ABCD]"
  • เลือกเป็นช่วงที่กำหนด เช่น ตัว a จนถึง x พิมพ์เล็ก ก็ใช้ "[a-x]"

ถึงตรงนี้ พอจะนึกออกแล้วใช่มั้ยฮะ ว่าเราสามารถใช้ "[]" แทนบาง class ได้ เช่น

  • "\d" แทนด้วย "[0-9]"
  • "\D" แทนด้วย "[^0-9]"
  • "\w" แทนด้วย "[a-zA-Z]"
  • "\W" แทนด้วย "[^a-zA-Z]"

Quantifier

เลือก class ได้แล้วต่อมาก็ต้องเลือกจำนวนกันฮะ

น้อยสุด มากสุด ต่อท้ายด้วย
0 เท่าไรก็ได้ *
1 เท่าไรก็ได้ +
0 1 ?
3 3 {3}
3 9 {3,9}
3 เท่าไรก็ได้ {3,}

ตัวอย่างเช่น เราอยากได้ข้อความที่เป็นตัวเลขสามหลักตามด้วยตัวอักษรกี่ตัวก็ได้ ก็จะเป็น "\d{3}\w*"

Alteration

สมมติว่าเราต้องการตัวเลือกให้ใช้เครื่องหมาย “|” คั่นแต่ละตัวเลือก เช่น

  • "a|b" เลือก "a" หรือ "b"
  • "cat|dog" เลือก "cat" ไม่ก็ "dog"

Anchor

Anchor เอาไว้กำหนดขึ้นต้นและลงท้าย มีสี่แบบดังนี้ฮะ

  • ขึ้นต้นบรรทัด ใช้ "^" เช่น ขึ้นต้นบรรทัดด้วย "a" ก็เป็น "^a"
  • ลงท้ายบรรทัด ใช้ "$" เช่น จบบรรทัดด้วย "z" ก็เป็น "z$"
  • ลงท้ายคำด้วยตัวที่ไม่ใช่ตัวอักษร ใช้ "\b" มาจาก boundary ฮะ เช่น "x\b" แปลว่า มันจะตัดที่อักษร x โดยที่ตัวถัดไปไม่ใช่ตัวอักษร ซึ่งอาจเป็น "x.", "x;", "x!" แต่ไม่รวมถึง "xa", "xx"
  • ลงท้ายคำด้วยตัวอักษร ใช้  "\B" เช่น "x\B" แปลว่า ตัดที่อักษร x โดยที่ตัวถัดไปเป็นตัวอักษร เช่น "xa", "xx", "xz" แต่ไม่ใช่ "x.", "x+"

Escape characters

คล้ายๆ กับภาษาอื่นฮะ เราใช้ backslash “\” นำหน้า เพื่อให้แสดงตัวอักษร escape characters เช่น

  • "*" ก็ใช้ "\*"
  • "." ก็ใช้ "\."
  • "$" ก็ใช้ "\$" เป็นต้นฮะ

Capture group

ช้วงเล็บครอบกลุ่ม REGEX ที่เราต้องการเลือก ก็จะได้ capture group มาแล้ว ทีนี้เราจะสามารถใช้ฟีเจอร์ต่อไปนี้ได้ฮะ

  • เลือกโดยอ้างอิง capture group ก่อนหน้าได้ โดยใช้ "\ตัวเลข" แทนลำดับของ group เช่น "(a|b|c)\1" คือ capture group ที่ 1 เป็นตัวอักษร a, b หรือ c ตามด้วยการอ้างอิง group ที่ 1 ผลลัพท์คือ ตรวจจับ "aa", "bb", หรือ "cc"
  • เลือกโดยอ้างอิงชื่อของ capture group แปลว่าเราจะต้องตั้งชื่อก่อน แล้วอ้างอิงด้วยชื่อทีหลัง เช่น "(?'x1'(a|b|c))\k'x1' " จะได้ผลลัพท์เหมือนกับข้อข้างบนฮะ
  • ใช้คู่กับฟังก์ชันประเภทแทนค่า (substitution) หรือสกัดออก (extraction) ในส่วนที่ต้องการ เช่น เปลี่ยนทุกตำแหน่งในข้อความที่เป็นตัวเลขให้เป็นตัว “x” หรือสกัดข้อความย่อยที่เป็นตัวเลขติดกับตัวอักษร “a” ออกมา

อุปกรณ์เสริม

เว็บสองตัวนี้เป็นตัวช่วยของผมเพื่อเช็ค REGEX ว่าถูกต้องรึเปล่าฮะ

ลองใช้งานจริงกัน

SQL บน Google BigQuery

ใน Google BigQuery มัน support REGEX ค่อนข้างดีเลยฮะ นี่คือตัวอย่าง

WITH test_set AS (
  SELECT ["+66876543210", "0812345678", "9876543210987",
    "[email protected]", "[email protected]",
    "[email protected]",
    "1234567890123", "9876543210987", "#iphone12mini", "#รักเธอที่สุด"
    ] AS text 
)
SELECT text, 
  regexp_contains(text, r'^0[689]\d{8}$') as is_mobile,
  regexp_contains(text, r'[\d\w\-_\.]+\@[\d\w\-_\.]+\..*') as is_email,
  regexp_contains(text, r'^\d{13}$') as is_thaiid,
  regexp_contains(text, r'#[\p{Thai}\w\d_]+') as is_hashtag
FROM test_set, unnest(text) text

อธิบาย REGEX แต่ละอันของ query นี้ด้วยภาพด้านล่างฮะ

REGEX diagrams

และนี่คือผลลัพท์ฮะ ค่า true แปลว่า text เป็นประเภทนั้นโดยอ้างอิงจาก REGEX ที่กำหนด

ผลลัพท์การใช้ REGEX functions บน Google BigQuery

Python script

และสำหรับภาษา Python ผมเลือกใช้ library regex ที่ยืดหยุ่นกว่า library re ที่เป็น standard library ฮะ เพราะ re ไม่ support class "\p{language}"

import regex
test_set = ["+66876543210", "0812345678", "9876543210987",
            "[email protected]", "[email protected]", 
           "[email protected]", "1234567890123", 
            "9876543210987", "#iphone12mini", "#รักเธอที่สุด"] 

rgx_mobile = "^0[689]\d{8}$"
rgx_email = "[\d\w\-_\.]+\@[\d\w\-_\.]+\..*"
rgx_thaiid = "^\d{13}$"
rgx_hashtag = "#[\p{Thai}\w\d_]+"

for t in test_set:
    if regex.match(rgx_mobile, t) is not None:
        print(t, "is mobile")
    elif regex.match(rgx_email, t) is not None:
        print(t, "is email")
    elif regex.match(rgx_thaiid, t) is not None:
        print(t, "is thaiid")
    elif regex.match(rgx_hashtag, t) is not None:
        print(t, "is hashtag")
    else:
        print(t, "is others")
ผลลัพท์การใช้ REGEX methods บน Python

จุดที่ต้องระวัง

จริงอยู่ว่า REGEX ที่เล่าไปมันมี pattern ที่ค่อนข้างชัดเจน แต่จุดสำคัญคือ REGEX engine แตกต่างกัน เช่น reของ python จะมีบางจุดแตกต่างจาก bigquery ที่ใช้ re2 ของ golang ทำให้บาง class ใช้ร่วมกันไม่ได้ หรือแม้ python เองก็มี regex และ re library ที่แตกต่างกันที่ได้บอกไปแล้วข้างต้น

ตรงนี้เลยเป็นประเด็นที่ต้องคำนึงถึงว่าเรากำลังใช้ engine อะไรในการ compile REGEX อยู่ เพื่อไม่ให้เกิด error ได้ฮะ

ข้อมูลเพิ่มเติมของ REGEX engine นี้ ดูได้ที่ https://en.wikipedia.org/wiki/Comparison_of_regular-expression_engines


คิดว่าในสายงานโปรแกรมเมอร์และสายเดต้าจะต้องมีโอกาสได้ใช้ REGEX นี้แน่นอน ไม่มากก็น้อย ฝึกให้ชินไว้บ้างก็ไม่เสียหายนะฮะ