對於傳統的前端工程師來講,更多的只是把服務端響應的數據渲染到頁面上。html
隨着前端的不斷進化,算法對咱們而言也日漸重要,大公司爲了優中選優,常常也會用算法題去篩選更優秀的人才。前端
算法,或許在工做中不多能用到,就算能用到也是直接調現成的庫函數,但在求職時倒是一個不可忽視的因素,總之機遇和挑戰並存吧!算法
本文是對數組去重這個常規算法題的手撕分析及拓展,但願可以給讀者帶來無形的財富。編程
傳統數組去重,是很硬氣的方式,去跟問題硬剛,來完成最直白的數組去重,是種笨辦法,但頗有效果。數組
首先,在一個數組中,要對一個數組去重,先想到的必定是讓全部元素跟其它元素比較一下,而後刪掉相同的。前端工程師
沒錯,我也是這麼想的,咱們反手就是一個嵌套循環:閉包
for (let i = 0; i < arr.length; i++) { for (let j = 0; j < arr.length; j++) { // 寫判斷條件 } }
這樣就構成了i和j雙指針聯動的形式,但是仔細思考,這好像世界盃足球運動員互相握手的模型。函數
我跟你握手了,這就算完成了,難道你還要再跟我握手嗎?oop
因此,受到啓發,咱們修改內層循環的初始條件。spa
for (let i = 0; i < arr.length; i++) { for (let j = i + 1; j < arr.length; j++) { // 寫判斷條件 } }
把j=0,改爲j=i+1。這樣就有效避免了重複握手的狀況。
接着,咱們須要判斷值是否同樣,因此能夠比較一下,兩個同樣的話就刪掉內層循環下標的元素。
if (arr[j] === arr[i]) { arr.splice(j, 1); }
注:splice是JS原生語法,須要用數組對象去調用,第一個參數是要調用他的數組須要切掉的下標,第二個參數是日後切幾個。
咱們加上調用代碼:
function arrayOutRepeat(arr) { for (let i = 0; i < arr.length; i++) { for (let j = i + 1; j < arr.length; j++) { if (arr[j] === arr[i]) { arr.splice(j, 1); } } } console.log(arr); } arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 3, 2, 3, 1]);
這樣咱們就寫好了一個算法,咱們進行調用試試吧。
結果出現了問題,爲何沒有刪清楚呢?
原來!咱們在splice以後,j就++了,至關於跳過一個元素。
那咱們就讓j往回再指一下,讓j--,再試試。
如今正常了,因此上完整代碼(手撕終版):
function arrayOutRepeat(arr) { for (let i = 0; i < arr.length; i++) { for (let j = i + 1; j < arr.length; j++) { if (arr[j] === arr[i]) { arr.splice(j, 1); j--; } } } return arr; } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 3, 2, 3, 1]); console.log(array);
根據前邊的詳解,咱們大致可以明確傳統去重的過程。
咱們還能夠換種思惟,將數組排好序,而後讓相鄰的元素比較。
這樣的代碼是很是簡單的,也能夠說是巧妙解決問題。
function arrayOutRepeat(arr) { arr.sort(); for (let i = 0; i < arr.length; i++) { if (arr[i] === arr[i + 1]) { arr.splice(i, 1); i--; } } return arr; } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(array);
咱們再換種思路,能夠聲明一個空數組,用新數組比較舊數組,要是沒有就添加。
這裏使用了indexOf方法,這個方法有必定的兼容性問題,IE低版本慎用!
indexOf方法須要用新數組去調用,參數爲舊數組中的第i個元素,返回值若是爲-1則表示沒有找到。
咱們能夠利用這一點,去添加舊數組裏沒有的元素。
function arrayOutRepeat(arr) { let arrNew = []; for (let i = 0; i < arr.length; i++) { if (arrNew.indexOf(arr[i]) == -1) { arrNew.push(arr[i]); } } return arrNew; } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(array);
在這裏,ES6還有一個includes方法,一樣的思路。
function arrayOutRepeat(arr) { let arrNew = []; for (let i = 0; i < arr.length; i++) { if (!arrNew.includes(arr[i])) { arrNew.push(arr[i]); } } return arrNew; } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(array);
除了上述三種最常規的去重手段以外,還有很多精簡的解決方案,這裏簡單介紹一下。
ES6中的Set是一種集合形式,集合中的元素值是惟一的。
ES6中還對Array新增了一個靜態方法Array.from(),能夠把Set集合轉化成數組形式。
所以配合起來使用,效果更佳,代碼量少的可憐。
function arrayOutRepeat(arr) { return Array.from(new Set(arr)); } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(array);
在arrayOutRepeat方法中,只須要一句代碼便解決問題,這個代碼已經很是精簡了。
但是,還有更精簡的方法,真難以想象。
ES6中新增的擴展運算符,能夠強制Set集合類型轉換成數組,代碼量更是少的可憐。
function arrayOutRepeat(arr) { return [...new Set(arr)]; } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(array);
Map也是ES6中新加入的內容,是一種用鍵值對存儲數據的結構。
咱們能夠經過Map實例化的對象map,結合對象調用map封裝的API取到key值,再用擴展運算符強制類型轉換。
function arrayOutRepeat(arr) { let map = new Map(); for (let i = 0; i < arr.length; i++) { if (!map.has(arr[i])) { map.set(arr[i]) } } return [...map.keys()]; } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(array);
過濾器,顧名思義,把不符合條件的濾掉,符合條件的篩出。
其中item是第i項的值,index是索引,而indexOf方法查找方式是順序查找(從前日後)。
好比遍歷到了第二個1的位置,indexOf返回的索引值是第一個1的索引,以此類推。
因此經過比較,加上過濾器,把索引值對不上的所有濾掉,剩下的就是「精英」了。
function arrayOutRepeat(arr) { return arr.filter((item, index) => { return arr.indexOf(item) === index; }) } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(array);
遞歸在編程中,算是邏輯難度很大的部分。看似代碼簡潔,其實暗藏玄機。
這裏我將網上的代碼拆解簡化了一下,本身手寫一遍就能更清晰!代碼以下:
function arrayOutRepeat(arr) { arr.sort(); function loop(index) { if (index >= 1) { if (arr[index] === arr[index - 1]) { arr.splice(index, 1); } loop(index - 1); } } loop(arr.length - 1); return arr; } let array = arrayOutRepeat([1, 2, 3, 2, 1, 3, 4, 3, 4, 4, 4, 3, 2, 3, 1]); console.log(arrayOutRepeat(array));
解析點:
1.思路也是通過排序以後利用索引比較相鄰元素的值,進行去留判斷。(由後向前)
2.因爲形參arr做用域的關係,因此寫了個閉包,方便內層函數能夠引用外層函數的變量。
3.遞歸的結束條件,是當index<1時,也就是到首個元素時,遞歸進行回調。
以上就是經常使用的數組去重方法,雖然還有一些組合方法,但基本都是換湯不換藥,最重要的是思想!
注:當遇到引用數據類型時(數組,對象等),以上方法沒法對他們進行去重處理。
這時咱們就須要在判斷條件,利用instance操做符、isArray方法,constructor屬性等去深刻判斷。