φυβλαςのβλογ
บล็อกของ phyblas



javascript เบื้องต้น บทที่ ๑๔: การใช้เรกูลาร์เอ็กซ์เพรชชัน
เขียนเมื่อ 2019/07/31 23:24
แก้ไขล่าสุด 2021/09/28 16:42


ในบทที่แล้วแนะนำเมธอด .indexOf ซึ่งใช้ในการค้นหาข้อความที่ต้องการภายในสายอักขระไป

แต่ว่าหากต้องการค้นหาข้อความที่ไม่ใช่แค่แบบเดียวตายตัว แต่เป็นกลุ่มคำบางอย่างที่มีเงื่อนไขซับซ้อน กรณีแบบนี้นิยมใช้เทคนิคที่เรียกว่า เรกูลาร์เอ็กซ์เพรชชัน (regular expression) หรือเรียกย่อๆว่า regex หรือ regexp

ในที่นี้ต่อจากตรงนี้จะขอเรียกสั้นๆว่า regex

ในหน้านี้จะเน้นแค่เรื่องวิธีการใช้ regex ภายในจาวาสคริปต์ ส่วนเรื่องที่ว่า regex คืออะไร มีรูปแบบการเขียนอย่างไรสามารถดูได้ใน https://phyblas.hinaboshi.com/20190709

regex เป็นเรื่องที่ซับซ้อน และใช้เวลาศึกษานาน หากใครไม่ได้จำเป็นต้องใช้สามารถอ่านข้ามบทนี้ไปได้



ค้นหาคำที่เข้าข่าย

สายอักขระมีเมธอด search ใช้สำหรับค้นหาคำที่ต้องการโดยใช้ regex เมธอดนี้จะบอกตำแหน่งของตัวแรกที่พบว่าเข้ากับรูปแบบ regex ที่ต้องการค้นหา

วิธีใช้
สายอักขระ.search(/รูปแบบที่ต้องการค้น/)

รูปแบบ regex ที่ต้องการค้นนั้นจะล้อมด้วยสแลช / / แบบนี้ ต่างจากสายอักขระที่จะล้อมรอบด้วยเครื่องหมายคำพูด " "

โครงสร้างที่ล้อมด้วยสแลช / / นั้นเป็นวิธีการสร้างออบเจ็กต์สำหรับ regex ขึ้นมา เป็นออบเจ็กต์ชนิดหนึ่งที่ทำหน้าที่เก็บรูปแบบของ regex เอาไว้ใช้

ตัวอย่าง หาคำที่เป็นสระอา
var str = "เมื่อยามรักน้ำต้มผักก็ว่าหวาน";
alert(str.search(/[ก-ฮ]า[ก-ฮ]?/)); // ได้ 5

แบบนี้ จะตรงกับคำว่า "ยาม" ซึ่งอยู่ตำแหน่งที่ 5 (อักษรตัวที่หก) เลยได้คำตอบเป็นเลข 5

ออบเจ็กต์ regex นั้นก็เป็นออบเจ็กต์ชนิดหนึ่ง สามารถเก็บใส่ตัวแปรไว้ก่อนเพื่อนำมาใช้อีกทีได้
var reg = /.า.?/;
alert(str.search(reg));

หากหาไม่เจอจะได้ -1
alert("เก้า".search(/\d/)); // ได้ -1

ที่จริงแล้ว .search จะใส่สายอักขระธรรมดา ไม่ต้องเป็น regex ก็แต่ แต่แบบนี้ก็จะไม่ต่างอะไรกับใช้ .indexOf
alert("123".search('1')); // ได้ 0

แต่จริงๆก็สามารถเขียนรูปแบบ regex ลงในสายอักขระธรรมดาได้ เพียงแต่มีข้อควรระวังก็คือ หากมี \ อยู่จะต้องใส่เป็น ๒ ตัว เพราะสายอักขระธรรมดาจะตีความ \ เป็นตัวเอสเคป
alert("123".search("\d")); // ได้ -1 (หาไม่เจอ)
alert("123".search("\\d")); // ได้ 0


การค้นด้วย match

search นั้นแค่หาตำแหน่งของคำที่ต้องการค้นเท่านั้น หากต้องการจะได้คำที่เจอด้วยใช้ .match วิธีการใช้จะเหมือนกัน แต่ผลที่ได้จะออกมาเป็นคำที่ต้องการค้น

ตัวอย่าง
var str = "แมว 13 ตัว หนู 101 ตัว";
var mat = str.match(/\d+/);
alert(mat); // ได้ 13

ผลที่ได้จากการ match นั้นแม้จะแสดงผลเหมือนเป็นสายอักขระเวลาดูค่าด้วย alert แต่จริงๆไม่ใช่แค่สายอักขระ แต่จริงๆแล้วเป็น object มีพรอเพอร์ตีเพิ่มเติมที่สำคัญคือ index ซึ่งเป็นตัวบอกตำแหน่ง
alert(typeof mat); // ได้ object
alert(mat.index); // ได้ 4

ดังนั้นเมื่อใช้ match จะได้ทั้งข้อความที่ต้องการหาและยังได้ตำแหน่งของมันมาด้วยในเวลาเดียวกัน



การค้นหาทีละหลายตัว

ปกติแล้วเวลาค้นด้วย match จะเอาเฉพาะผลตัวแรกที่หาเจอ แต่หากอยากให้หาหลายตัวเอาทุกตัวที่มีอยู่ให้ใส่ตัวเลือกเสริม g ไว้ข้างหลัง / / กลายเป็น / /g แบบนี้ผลที่ได้จะกลับมาเป็นแถวลำดับของค่าที่หาเจอ

ตัวอย่าง
var str = "121~471#33*8657";
alert(str.match(/\d+/g)); // ได้ 121,471,33,8657

เพียงแต่ว่าเมื่อใช้ g แบบนี้แล้วผลที่ได้จะเป็นสายอักขระธรรมดา จะไม่สามารถใช้ .index ต่อท้ายผลลัพธ์เพื่อหาตำแหน่งได้



การทำให้ไม่แยกตัวพิมพ์เล็กพิมพ์ใหญ่

หากต้องการให้เวลาค้นหาไม่มีการแยกความต่างระหว่างตัวพิมพ์เล็กและพิมพ์ใหญ่ให้เติมตัวเลือกเสริม i

ในขณะเดียวกันก็สามารถใส่ g ไปด้วยได้ ในกรณีแบบนี้ก็เขียนต่อกันไปได้เลย

ตัวอย่าง
var str = "aAáÁậẬ";
alert(str.match(/[aáậ]/g)); // ได้ a,á,ậ
alert(str.match(/[aáậ]/gi)); // ได้ a,A,á,Á,ậ,Ậ



การให้ถือว่าขึ้นบรรทัดใหม่เป็นจุดเริ่มต้นของคำ

ปกติแล้วถ้าใช้ ^ หรือ $ เพื่อกำหนดให้เฉพาะตัวที่อยู่ต้นหรือปลายเท่านั้นที่จะเข้าข่ายได้ แบบนี้ ^ จะพิจารณาเฉพาะตำแหน่งแรกสุดของสายอักขระ และ $ จะดูแค่ตำแหน่งสุดท้าย

แต่หากต้องการให้การขึ้นบรรทัดใหม่ทั้งหมดถือว่าการเริ่มคำใหม่ สามารถทำได้โดยใส่ตัวเลือกเสริม m

ลองดูความต่างระหว่างใส่ m กับไม่ใส่
var str = "กาก\nกาบ\nกาด";
alert(str.match(/[กบด]$/g)); // ได้ ก,บ,ด
alert(str.match(/[กบด]$/gm)); // ได้ ด



การแทนที่คำที่ต้องการด้วยอีกคำ

ในบทที่แล้วได้แนะนำเมธอด replace ไป แต่เมธอดนี้ถ้าใช้กับ regex ก็จะทำอะไรได้มากขึ้น

วิธีการใช้ replace คู่กับ regex
สายอักขระ.replace(/รูปแบบ regex/, สิ่งที่ต้องการแทน)

ตัวอย่าง
var str = "takara";
alert(str.replace(/[ae]/g, "o")); // ได้ tokoro

ตรงส่วนที่ต้องการแทนนั้นสามารถใช้ฟังก์ชันได้ โดยฟังก์ชันนั้นจะรับพารามิเตอร์ตัวแรกเป็นข้อความที่เข้าข่ายทั้งหมด ส่วนตัวถัดไปจะแทนตัวที่จัดกลุ่มไว้ด้วยวงเล็บ ตามลำดับ

ตัวอย่าง
var f = function(m, a, b) {
  if (b == "า") return "โ" + a;
  else return "โ" + a + b;
};
var str = "ทาการะ";
str = str.replace(/([ก-ฮ])([าะ])/g, f);
alert(str); // ได้ โทโกโระ



การแบ่งสายอักขระโดยใช้คำที่ต้องการเป็นตัวแบ่ง

เมธอด .split ใช้ในการแยกสายอักขระโดยใช้คำที่กำหนดเป็นตัวแบ่ง

การกำหนดคำที่ใช้แบ่งนั้นนอกจากจะใช้ตัวอักษรหรือข้อความใดๆตายตัวแล้ว เราสามารถใช้ regex เพื่อกำหนดให้คำที่ใช้แบ่งเป็นอะไรที่ยืดหยุ่นขึ้น
var str = "ก|ข^ค-ง";
alert(str.split(/[|^-]/)); // ได้ ก,ข,ค,ง



การสร้างออบเจ็กต์ regex

การสร้างออบเจ็กต์สำหรับ regex นั้น นอกจากใช้ล้อมด้วยสแลช / / แล้วยังอาจสร้างขึ้นโดยใช้คอนสตรักเตอร์ RegExp

รูปแบบการสร้าง
var ตัวแปร = new RegExp(รูปแบบการค้น, ตัวเลือกเสริม);

ตัวอย่าง
var reg = new RegExp(".a+", "gi");

จะมีความหมายเหมือนกับ
var reg = /.a+/gi;

วิธีอาจดูเหมือนจะยาวขึ้น แต่จะสะดวกที่จะใช้เมื่อเรามีสายอักขระที่เขียนรูปแบบ regex อยู่แล้วต้องการแปลงเป็นออบเจ็กต์ regex
var strreg = "\\d\\d?"
var reg = new RegExp(strreg);

แต่การสร้างสายอักขระที่เขียน regex นั้นต้องอย่าลืมใส่แทน \ ด้วย \\ ไม่เช่นนั้นจะเกิดข้อผิดพลาดได้

ออบเจ็กต์ regex นั้นมีพรอเพอร์ตีดังนี้

source ข้อความแสดงรูปแบบ regex ที่เราใส่ไป
global หาหลายตัวหรือไม่ (ใส่ g หรือไม่)
multiline ให้แต่ละบรรทัดเป็นจุดเริ่มคำหรือไม่ (ใส่ m หรือไม่)
ignoreCase ไม่แยกตัวพิมพ์เล็กพิมพ์ใหญ่หรือไม่ (ใส่ i หรือไม่)
lastIndex ดัชนีสุดท้ายเมื่อใช้ .exec (หัวข้อถัดไป)


ตัวอย่าง
var reg = /\d+/gm;
alert(reg.source); // ได้ \d+
alert(reg.global); // ได้ true
alert(reg.multiline); // ได้ true
alert(reg.ignoreCase); // ได้ false
alert(reg.lastIndex); // ได้ 0



เมธอด exec

ออบเจ็กต์ regex นั้นมีเมธอด exec ซึ่งทำงานคล้ายกับเมธอด search ในสายอักขระ เพียงแต่ต่างกันตรงที่เป็นเมธอดที่ใช้กับออบเจ็กต์ regex โดยมีสายอักขระเป็นอาร์กิวเมนต์

นอกจากนี้ยังมีความแตกต่างอีกอย่าง คือกรณีที่ใช้กับตัวเลือกเสริม g เพื่อให้ค้นได้หลายตัว

เมื่อใส่ตัวเลือกเสริม g ไป ถ้าใช้ .search จะได้ผลลัพธ์ออกมาหลายตัวแต่ว่าแต่ละตัวจะเป็นสายอักขระธรรมดาที่ไม่มีข้อมูลตำแหน่ง

แต่ถ้าใช้ .exec จะหาแค่ตัวเดียวก่อนแล้วจะมีการบันทึกตำแหน่งที่หาไปถึงไว้ จะหาตัวต่อไปก็ต้อง .exec ซ้ำอีกรอบ แล้วการหาก็จะเริ่มจากตำแหน่งนั้น

นอกจากนี้การหาในแต่ละรอบจะได้ออบเจ็กต์ผลการค้นซึ่งดู .index ได้หมด ดังนั้นการใช้จึงยืดหยุ่นกว่า .search

ตัวอย่าง
var reg = /[^aiueo]?[aiueo]/g;
var str = "takaoka";
var mat = reg.exec(str);
alert([mat, mat.index, reg.lastIndex]); // ได้ ta,0,2
mat = reg.exec(str);
alert([mat, mat.index, reg.lastIndex]); // ได้ ka,2,4
mat = reg.exec(str);
alert([mat, mat.index, reg.lastIndex]); // ได้ o,4,5

สามารถใช้กับ while ได้
var reg = /[^aiueo]?[aiueo]/g;
var mat;
var str = "tapioka";
var s = "";
while ((mat = reg.exec(str)) != null) {
  s += "เจอ " + mat + " ที่ตำแหน่ง " + mat.index + "\n";
}
alert(s);

ผลลัพธ์
เจอ ta ที่ตำแหน่ง 0
เจอ pi ที่ตำแหน่ง 2
เจอ o ที่ตำแหน่ง 4
เจอ ka ที่ตำแหน่ง 5

โค้ดอาจเข้าใจยากสักหน่อย ในที่นี้ mat = reg.exec(str) คือการที่ให้ในแต่ละรอบป้อนค่าให้ตัวแปร mat

ส่วนที่เติม != null ไว้ข้างหลังเป็นการให้เปรียบเทียบไปด้วยว่าค่าในแต่ละรอบนั้นยังมีอยู่หรือเปล่า หาก exec หาไม่เจอแล้วก็จะได้ค่า null ออกมา จึงหลุดออกจากวังวน while ไปได้ แต่ถ้ายังมีค่าก็จะถูกแทนเข้าตัวแปร mat แล้วก็วนซ้ำต่อไป

หากรู้สึกว่าการเอาค่าไปใส่ในวงเล็บหลัง while ทำให้ดูยาก สับสนง่าย อาจแก้เป็นโครงสร้างที่ใช้ break แบบนี้แทนได้ ผลเหมือนกัน
while (true) {
  mat = reg.exec(str);
  if (mat == null) break;
  s += "เจอ " + mat + " ที่ตำแหน่ง " + mat.index + "\n";
}




เมธอด test

ออบเจ็กต์ regex ยังมีเมธอด test เอาไว้หาว่าข้อความมีส่วนที่เข้ารูปแบบใน regex นั้นอยู่หรือไม่

เมธอดนี้ทำได้แค่ให้ค่า true หรือ false ถ้ามีส่วนไหนของสายอักขระที่เข้าข่ายสักที่ก็ได้ true ถ้าไม่มีเลยก็ได้ false

ตัวอย่าง
alert(/\d/.test("12")); // ได้ true
alert(/\d/.test("ab")); // ได้ false
alert(/\d\d/.test("1a")); // ได้ false




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript
-- คอมพิวเตอร์ >> เขียนโปรแกรม >> regex

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文