正規表達式筆記:斷言(assertions)

總結

此篇文章是閱讀 MDN: Regular expressions > Assertions 時記下的筆記。

筆記內容

定義

MDN: Assertions include boundaries, which indicate the beginnings and endings of lines and words, and other patterns indicating in some way that a match is possible (including look-ahead, look-behind, and conditional expressions).

assertions 字元讓開發者可以根據「邊際」或「前後符合條件」來進行搜尋。

邊際類型(boundary-type assertions)

^:比對一段內容的「起點」是否符合 ^ 後面提供的條件。舉例:/^a/ 會挑中字串 apple 中的 a,但不會挑中 banana 中的 a

$:比對一段內容的「結尾」是否符合 $ 前面提供的條件。舉例:/t$/ 會挑中字串 cat 中的 t,但不會挑中 tea 中的 t

\b 代表「邊際」(word boundary),範例如下:

\B 的作用與 \b 相反,代表「非邊際」(non-word boundary);如果把上方範例中的 \b 改為 \B,結果就會反過來:

前後條件類型

x(?=y):挑出後方有 yx。舉例:apple(?= pie) 會挑中 apple pie 字串中的 apple,但不會挑中 apple tea 中的 apple

x(?!)y:挑出後方沒有 yx。舉例:apple(?! pie) 會挑中 apple tea 字串中的 apple,但不會挑中 apple pie 中的 apple

(?<=y)x:挑出前方有 yx。舉例:(?<=apple )pie 會挑中 apple pie 中的 pie,但不會挑中 cherry pie 中的 pie

(?<!y)x:挑出前方沒有 yx。舉例:(?<!apple )pie 會挑中 cherry pie 中的 pie,但不會挑中 apple pie 中的 pie

MDN 範例解讀

buggyMultiline = `tey, ihe light-greon apple
tangs on ihe greon traa`;

buggyMultiline = buggyMultiline.replace(/^t/gim, 'h');
// 將位於每一個「單字開頭」的 t 替換成 h
// 這裡會將 tey 與 tangs 換成 hey 與 hangs

buggyMultiline = buggyMultiline.replace(/aa$/gim, 'ee.');
// 將每一個「單字結束前」的 aa 替換為 ee.
// 這裡會將 greon traa 的 traa 換成 tree

buggyMultiline = buggyMultiline.replace(/\bi/gim, 't');
// 將每一個「前方有字元邊際」的 i 替換為 t
// 這裡會將出現兩次的 ihe 替換為 the

fixedMultiline = buggyMultiline.replace(/\Bo/gim, 'e');
// 將每一個被其他字母包圍的 o 替換為 e
// 這裡會將出現兩次的 greon 替換為 green

const fruits = ['Apple', 'Watermelon', 'Orange', 'Avocado', 'Strawberry'];

const fruitsStartsWithA = fruits.filter((fruit) => /^A/.test(fruit));
// 取出「開頭為大寫字母 A」的字串

const fruitsStartsWithNotA = fruits.filter((fruit) => /^[^A]/.test(fruit));
// 取出「開頭『不是』大寫字母 A」的字串
// [^A] 代表「開頭為大寫字母 A」,但搭配 ^ 執行反轉後,條件改為「開頭不為大寫字母 A」

const fruitsWithDescription = ['Red apple', 'Orange orange', 'Green Avocado'];

const enEdSelection = fruitsWithDescription.filter((descr) =>
  /(en|ed)\b/.test(descr)
);
// 取出「以 en 或 ed 結尾」的字串

const regex = /First(?= test)/g;

翻譯:比對出後方緊隨 testFirst

若傳入字串 First test ,結果會是比對成功 若傳入字串 First kiss ,結果會是比對失敗,因為 First 後方接續的不是 test 而是 kiss


const regex = /\d+(?!\.)/g;

翻譯:比對出後方沒有 . 的「一個或無限多個阿拉伯數字」(\d

步驟分解如下:

合起來的意思就是「搜尋所有後方沒有出現 . 的阿拉伯數字,且這段阿拉伯數字可能為 1 至無限多個」。

舉例:透過此 regex 條件搜尋字串 3.14 時,回傳出來的比對結果會是 14,因 3 後方有 .,不符合條件;而 14 後方沒有 .,故比對成功。

注意:如果將條件中的 + 拿掉,那麼 regex 的意義會變為「後方沒有 . 的『一個』阿拉伯數字」。透過此條件比對 3.14 時,會回傳兩個數字,分別是 14


const orangeNotLemon =
  'Do you want to have an orange? Yes, I do not want to have a lemon!';

const selectNotLemonRegex = /[^?!]+have(?! a lemon)[^?!]+[?!]/gi;
console.log(orangeNotLemon.match(selectNotLemonRegex));
// [ 'Do you want to have an orange?' ]

const selectNotOrangeRegex = /[^?!]+have(?! an orange)[^?!]+[?!]/gi;
console.log(orangeNotLemon.match(selectNotOrangeRegex));
// [ ' Yes, I do not want to have a lemon!' ]

分解 /[^?!]+have(?! a lemon)[^?!]+[?!]/gi 步驟如下:

總結 selectNotLemonRegex 的意思為:搜尋不是 ?! 的內容(且要有兩段,因為 [^?!]+ 出現兩次),兩段內容中間要夾帶一個「後方不是 a lemonhave」,最後允許 ?! 做為結尾。

selectNotOrangeRegex 僅替換了中間搜尋 have 的條件為「後方不是 an orange」,其餘邏輯與 selectNotLemonRegex 相同。


const oranges = ['ripe orange A', 'green orange B', 'ripe orange C'];
const ripeOranges = oranges.filter((fruit) => /(?<=ripe )orange/.test(fruit));

翻譯:比對前方有 ripeorange

陣列 oranges 中有 ripe orange Aripe orange C 符合條件;而 green orange Borange 前方的字串為 green 故判定為比對失敗。

參考文件