有很多種方法能實現陣列濾重功能,有人統計過在 JS
裡至少就有 10
種方式。
本文關心的是:能否用正則來實現濾重這個功能呢?
誠然,就算能實現,估計也沒人會把它當成最佳實踐的。
所以這裡,我們只考慮可能性。
本文給出的答案:可以!而且不止一種方式。
下面我們從易到難一步步來看如何實現的。
1. 相鄰字元濾重問題
"abbccc" => "abc"
正則裡要匹配之前出現過的字元,需要使用反向引用:
function distinct(string) {
return string.replace(/(.)\1+/g, '$1')
}
console.log(distinct("abbccc"))
// => "abc"
複製程式碼
其中 \1
是反向引用,指代第一個括號捕獲的資料,其中稱為 (.)
為捕獲分組。而 $1
也表示第一個括號捕獲的資料。具體過程請看下圖。
其中藍色表示捕獲分組捕獲到的資料,粉色的表示反向引用指代的資料。進行替換操作後帶顏色的資料只保留了藍色資料。
2. 字串濾重
"abbacbc" => "abc"
方式一
一般的字串這麼辦呢?
最直接的思路是把問題轉化為已解決過的問題。
把字串拆分成陣列,然後位元組碼排序,轉化成相鄰字元濾重問題。
這種方式,用了陣列相關方法,正則的意味就沒那麼濃烈了。
方式二
使用迴圈,刪除重複出現的字元。
function distinct(string){
while(/(.).*?\1/.test(string)) {
string = string.replace(/(.)(.*?)\1/, '$1$2')
}
return string;
}
console.log(distinct("abbacbc"))
// => "abc"
複製程式碼
用正則 /(.).*?\1/
來判斷字串裡是否還有重複字元,有的話,就替換一下。
替換的正則是 /(.)(.*?)\1/
,其中使用了兩組括號,為引用 $1
和 $2
提供了資料。具體過程示圖如下:
方式三
方式二里使用了迴圈,總覺得有點太笨。其實可以直接使用 replace
。此時需要使用 (?=p)
:
function distinct(string) {
return string.replace(/(.)(?=.*?\1)/g, '')
}
console.log(distinct("abbacbc"))
// => "abc"
複製程式碼
具體過程示圖如下:
(?=.*?\1)
表示匹配位置,即圖中綠色箭頭所示。如第一行中字元 a
後面的位置,改位置後面的字元匹配 .*?\1
,其中 \1
即圖中粉色的資料,對應於第一個分組捕獲的藍色資料。最後所有的藍色資料都被替換成 ''
了。
這種實現方式有一個問題,就是重複字元只保留最後出現的字元。如果在原來字串後面加個 "a"
變成 "abbacbca"
,最終結果卻是 "bca"
。
方式四
方式三的思路是看當前字元是否會在後面出現,如果出現就刪除。方式四的邏輯卻可以說反過來的:如果當前字元在前面出現過,那麼就刪除。此時需要用斷言 (?<=p)
,看當前位置前面是否匹配 p
。
正則不能想當然地寫成 /(?<=.*?\1)(.)/g
,因為 \1
是“反向”引用,只能引用它之前的分組。所以這裡要把它放在目標字元後面:
function distinct(string) {
return string.replace(/(.)(?<=\1.*?\1)/g, '')
}
console.log(distinct("abbacbc"))
// => "abc"
複製程式碼
具體過程如下:
比如圖中第一行中第二個b後面的綠色箭頭表示(?<=\1.*?\1)
。第一個 \1
是粉色 b
,第二個是藍色的那個。
3. 陣列濾重
有字串濾重後,陣列濾重就簡單了。上面四種方法都可以寫成陣列版本的。比如第四種方案如下:
function distinct(arr) {
return arr.join('').replace(/(.)(?<=\1.*?\1)/g, '').split('')
}
console.log(distinct(['a','b','b','a','c','b','c']))
// => ['a', 'b', 'c']
複製程式碼
至此我們的解決方案還有一些問題:
- 只能過濾陣列的每個元素是一個字元的情形
- 過濾的結果會把元素轉化為字元。
支援多位字元相對容易解決,但是要保持型別的話,需要JSON兩個方法了。
最後給出方案四的最終版本:
function distinct(arr) {
var string = JSON.stringify(arr)
string = string.replace(/,([^,]+)(?<=\1.*?\1)(?=,|])/g, (m, $1) => $1 == '"' ? m : '')
return JSON.parse(string)
}
console.log(distinct(["aa",1,"ab",true,1,true,"aa"]))
// => ["aa", 1, "ab", true]
複製程式碼
本文完。
另外,歡迎閱讀本人的《JS正則迷你書》。