2020-04-28

regular expression 的 Lookahead 與 Lookbehind

[regular expression]
這裡不教導簡單的 regular expression, 首先你必需瞭解一些基本的 RE。例如 g 是指全域尋找,[56]是指 5 或 6,[2-8]是指 2 到 8。
基本的 RE 很簡單,到處都有資料,要瞭解沒有任何困難。

【開始】

所謂 regular expression 的 expression 是指表達式,也就是說尋找「符合整個表達式的規則」的資料的意思。
對程式設計師來說落落長的「說明」是沒太大用處的,程式碼才能表達一切。但是上面這句話卻是例外,不領會這句話的意義就代表不懂或一知半解 regular expression 的意思。

0. 基礎說明

在這裡我們將 RE 的格式 (?=c)m 中,c 我們稱其為「條件表達式」或「條件式」,而 m 稱之為「主表達式」
各種格式例如以下:
(?=c)m、m(?=c)、(?<=c)m、m(?<=c)

Lookahead符合條件時從前面開始抓資料。當條件式在主表達式「前面」時則取值判斷時包含該條件式,在後面時則看取多少「長度」。
Lookbehind符合條件時從後面開始抓資料。 當條件式在主表達式「後面」時則取值判斷時包含該條件式,在後面時則看取多少「長度」。
positive: 要符合條件式,「必需」符合條件式
negative: 要不符合條件式, 「不可」符合條件式(限制不能同條件式所述)
front: 條件式在主表達式「前面」的這種 expression
behind: 條件式在主表達式「後面」的這種 expression

RE 條件比對順序:RE 條件比對順序為由左至右比對,所以前後順序很重要

1. lookahead 及 lookbehind

   很多人跟你說 lookahead 就是往前看,所以格式 m(?=c) 就是找 m 後面有 c 的資料。
   誰跟你說的?
   包含外國人都跟你說 m followed by c, 其實這是大大的誤解了。
   難道我不能這樣找 (?=c)m ,意指找 c 在 m 前的「符合這個表達式規則」的資料? 這時後難道要叫「往後看」?lookbehind?
   很顯然的並不是這樣的,因為 lookbehind 在 RE 裡是另一個意思。
   RE 最重要的是「表達式順序」,
   lookahead 意指若條件式(c)在前面時,則取值判斷時要包含 c 值,但條件式若在後面時取值判斷時則不包含 c值。即符合條件時從前面開始抓資料。相反的,
   lookbehind 意指若條件式(c)在前面時,則取值判斷時不包含 c 值,但條件式若在後面時取值判斷時則要包含 c值。即符合條件時從後面開始抓資料。

2. 用例子證明一切

所以到這裡我們總共有 lookahead/lookbehind, positive/negative, condition position front/behind 共 2 x 2 x 2 = 8 種狀況,
再加上我們再用長度 1 及 2 讓觀念更清楚,於是共有 8 x 2 = 16 種狀況
# Lookahead
## positive Lookahead (positive ?=)
### positive Lookahead, 1 char, condition in front
"01234567890".match(/(?=[5])[2-8]/g)
結果: ["5"]
分析: 因為 lookahead 在 front 時取值判斷要包含其值,又我們只要1個字元,故直接只有 5 符合其值,毫無懸念。
### positive behind, 1 char, condition in behind
"01234567890".match(/[2-8](?=[5])/g)
結果: ["4"]
分析: 因為 lookahead 在 behind 時取值判斷"不"包含其值,又我們只要1個字元,因 4 的下1位5符合,取值不取5,故答案為 4
### positive Lookahead, 2 chars, condition in front
"01234567890".match(/(?=[5])[2-8]{2}/g)
結果: ["56"]
分析: 因為 lookahead 在 front 時取值判斷要包含其值,取2個字元,故直接只有 [5]6 符合其值
### positive behind, 2 chars, condition in behind
"01234567890".match(/[2-8]{2}(?=[5])/g)
結果: ["34"]
分析: 因為 lookahead 在 behind 時取值判斷"不"包含其值,取2個字元,故答案為 34[5]
## negative lookahead (negative ?!)
### negative lookahead, 2 chars, condition in front
"01234567890".match(/(?![6])[2-8]{2}/g)
結果: ["23", "45", "78"]
### negative lookahead, 2 chars, condition in behind
"01234567890".match(/[2-8]{2}(?![6])/g)
結果: ["23", "56", "78"]
## negative 1
### negative lookahead, 1 char, condition in front
"01234567890".match(/(?![5])[2-8]/g)
結果: ["2", "3", "4", "6", "7", "8"]
### negative lookahead, 1 chars, condition in behind
"01234567890".match(/[2-8](?![5])/g)
結果: ["2", "3", "5", "6", "7", "8"]


# Lookbehind
## positive
### positive 2 front
"01234567890".match(/(?<=[5])[2-8]{2}/g)
結果: ["67"]
分析: 因為 Lookbehind 在 front 時取值判斷"不"包含其值,取2個字元,5為前置不取,[5]67,故答案為 67
### positive 2 behind
"01234567890".match(/[2-8]{2}(?<=[5])/g)
結果: ["45"]
分析: 因為 Lookbehind 在 behind 時取值判斷要包含其值,取2個字元,故答案為 4[5] (含5)
### positive 1 front
"01234567890".match(/(?<=[5])[2-8]/g)
結果: ["6"]
### positive 1 behind
"01234567890".match(/[2-8](?<=[5])/g)
結果: ["5"]
## negative
### negative 2 front
"01234567890".match(/(?<![5])[2-8]{2}/g)
結果: ["23", "45", "78"]
分析: 因為 Lookbehind 在 front 時取值判斷不包含其值,這裡取2個字元,故第1組23,第2組45,第3組67時因前面為5,故不符合,跳至下一字元78
### negative 2 behind
"01234567890".match(/[2-8]{2}(?<![5])/g)
結果: ["23", "56", "78"]
分析: 因為 Lookbehind 在 behind 時取值判斷要包含其值,這裡取2個字元,故第1組23,當第2組45中遇到 5不符規則,跳至下一位元,第2組變56,第3組78 
### negative 1 front
"01234567890".match(/(?<![5])[2-8]/g)
結果: ["2", "3", "4", "5", "7", "8"]
分析: 因為 Lookbehind 在 front 時取值判斷不包含其值,這裡僅取1個字元,故「前值為5的6不合規則」
### negative 1 behind
"01234567890".match(/[2-8](?<![5])/g)
結果: ["2", "3", "4", "6", "7", "8"]
分析: 因為 Lookbehind 在 behind 時取值判斷要包含其值,這裡取1個字元,故當 「非5」 時即為答案