出處: https://www.hackerrank.com/challenges/js10-regexp-1/problem?isFullScreen=true
簡單地寫一下題目大綱:
用 Regular Expressions 驗證一個字串,規則如下:
- 字串是全英文不含數字, 空格或符號
- 字串長度大於 3
- 字串開頭必須是母音 a, e, i, o, u 其中之一
- 字串的結尾最後一個字等於第一個字
解法
一直很苦手的的 Regular Expressions。之前看了不少技術文章,仍然不得要領。 但在做完了這幾題後,開竅不少。
解題前先了解 js RegExp 的 .test 方法: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
接下來看標準表達式的基本寫法(很難從它的敘述直接看懂,要多看一些範例再和技術文章對照): https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Regular_Expressions
開始吧
以下是整個解題的思路和心得分享。
1. 從開頭開始下手
首先 [ ] 內,是允許的內容。 ^ 則是代表文字開頭。
const re = /^[aeiou]/ re.test('apple') // return true re.test('pan') // return false
如果沒有 ^ 號,則語意會變成,字串內必須包含 a, e, i, o, u 任一字元。
const re = /[aeiou]/ re.test('apple') // return true re.test('pan') // return true re.test('xyz') // return false
2. 中間與結尾
中間和結束字串必須一起考慮。結束字串的符號是 $ ,放在驗證字串的最後。
依照本題的規則,第二個字元可以是 a – z 其中的一個,參考標準表達式的寫法寫成 [a-z] 。
+ 代表的是 1 至多個內容,所以我們寫成下面的樣子:
const re = /^[aeiou][a-z]+$/ re.test('apple') // return true re.test('pan') // return false re.test('a pan') // return false re.test('apple pan') // return false
如果沒有 $ 號呢?
這邊代表 a, e, i, o, u 的後面一定是一個 [a-z]+ 的字串。但是之後的字串就不管了。
const re = /^[aeiou][a-z]+/ re.test('apple') // return true re.test('pan') // return false re.test('a pan') // return false re.test('apple pan') // return true re.test('apple/pan') // return true // 如果加 $,則在 [a-z]+ 後面就必須結束 const re_2 = /^[aeiou][a-z]+$/ re.test('apple') // return true re.test('pan') // return false re.test('a pan') // return false re.test('apple pan') // return false re.test('apple/pan') // return false
如果沒有 + 號呢?
+ 號代表 1 ~ n 個,把 + 號拿掉,表示後面接 1 個符合 [a-z] 規則的字串。 下方的寫法就是指驗證開頭兩個字。
const re = /^[aeiou][a-z]/ re.test('apple') // return true re.test('pan') // return false re.test('a pan') // return false re.test('apple pan') // return true // 如果加 $,則在 [a-z] 後面就必須結束,不可能超過兩個字串 const re_2 = /^[aeiou][a-z]$/ re.test('abc') // return false re.test('ab') // return true
衍伸
除了 + 號外,也可用 {1, }。
{a} 的用法:
表示需要出現幾次。 [a-z]{2} 表示兩碼任意的小寫英文字母。
{ a, b } 的用法:
a 代表只少需要出現幾次,b 代表最多出現幾次(可不填,代表無限多次)。
假設我們希望接下來的字元是 [a-z] 必須至少出現 2 次 ,可以寫成 {2,}
常見的用法縮寫:
+ 等同於 {1,}
* 等同於 {0,}
? 等同於 {0,1}
3. 變數的引用
官方的內文: 匹配 ‘x’ 並記住此次的匹配
使用 .test 方法時,可以在規則內依序引用 \1, \2, …, \n 代表的就是前面的 pattern。
前面的方法,我們已經可以驗證以下幾個條件:
- 字串是全英文不含數字, 空格或符號
- 字串開頭必須是母音 a, e, i, o, u 其中之一
const re = /^[aeiou][a-z]+$/ re.test('apple') // return true re.test('pan') // return true re.test('abba') // return true
我們用來匹配的文字用 (x) 記錄,在用 \1 匹配到結尾。 寫法如下:
const re = /(^[aeiou])[a-z]+\1$/ re.test('apple') // return false re.test('pan') // return false re.test('abba') // return true
以 “abba” 這個字串為例。 字首 “a” 成為了 (^[aeiou]) 內批配的結果,也是第一個結果 \1,我們把這個結果用在字尾的驗證: \1$ 。
我們把整段 Regular Expressions 翻譯成中文吧。
(^[aeiou]) 開頭必須是 a, e, i, o, u 裡面的其中一個字,然後記錄起來(\1)。
[a-z]+ 接下來的字串由至少 1 個小寫英文字組成。
\1$ 結尾前的字串,要跟 \1 一樣。
衍伸
符合以下條件下:
- 字串是全英文不含數字, 空格或符號
- 字串長度大於 3
- 字串開頭必須是母音 a, e, i, o, u 其中之一
結尾字必須等於開頭兩個字:
const re = /(^[aeiou][a-z])[a-z]*\1$/ re.test('abba') // return false re.test('abab') // return true
結尾字是第一和第二字其中一個:
x|y : 符合「x」或「y」。
const re = /(^[aeiou])([a-z])[a-z]*(\2|\1)$/ re.test('abcd') // return false re.test('abca') // return true re.test('abcb') // return true
結尾字不可以是第一個字:
const re = /^([aeiou])[a-z]+(?!\1)[a-z]$/ re.test('abcd') // return true re.test('abca') // return false re.test('abcb') // return true
結尾字不可以是第一個字或第二個字:
x(?!y) : 符合’x’,且後接的不是’y’。’y’為否定’x’存在的意義,後面不行前功盡棄(negated lookahead)。
這邊寫的有點複雜,基本上就是把 y 當作排除的條件。例如 (?![ab])[a-z] 就當作 a-z 排除 a, b。
const re = /^([aeiou])([a-z])[a-z]*(?!\1|\2)[a-z]$/ re.test('abcd') // return true re.test('abca') // return false re.test('abcb') // return false
到這裡,一般的格式驗證應該已經難不倒了。