φυβλαςのβλογ
บล็อกของ 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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文