正則實現數組濾重

有不少種方法能實現數組濾重功能,有人統計過在 JS 裏至少就有 10 種方式。數組

本文關心的是:可否用正則來實現濾重這個功能呢?bash

誠然,就算能實現,估計也沒人會把它當成最佳實踐的。post

因此這裏,咱們只考慮可能性。ui

本文給出的答案:能夠!並且不止一種方式。spa

下面咱們從易到難一步步來看如何實現的。3d

1. 相鄰字符濾重問題

"abbccc" => "abc" code

正則裏要匹配以前出現過的字符,須要使用反向引用:cdn

function distinct(string) {
  return string.replace(/(.)\1+/g, '$1')
}
console.log(distinct("abbccc"))
// => "abc"
複製代碼

其中 \1 是反向引用,指代第一個括號捕獲的數據,其中稱爲 (.) 爲捕獲分組。而 $1 也表示第一個括號捕獲的數據。具體過程請看下圖。blog

其中藍色表示捕獲分組捕獲到的數據,粉色的表示反向引用指代的數據。進行替換操做後帶顏色的數據只保留了藍色數據。排序

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正則迷你書》

相關文章
相關標籤/搜索