前言
關於數組去重,也算是一個比較常見的面試題了。但是有點開發經驗的同學又會發現,前端數組去重的操作很少見(也可能是我個人經驗不足遇到的比較少)。這是爲什麼呢?。我感覺可能是大部分的去重操作被後端處理了。個人見解!
雖然,前端很少用到。本着愛(kuai)與(su)和(zhang)平(xin)的精神,我們也來學習一波!
方法彙總
一、IndexOf
IndexOf是數組的一個原生方法,當你傳入一個參數的時候,它會返回入參數的索引值。如果未找到就返回 -1。利用這一特性,
function unique(array) {
var res = []
for(var i = 0; i < array.length; i++) {
if(res.indexOf(array[i]) === -1) {
res.push(array[i])
}
}
return res
}
相信一般求職者都能回答出這個,當初我在面試的時候,也回答了這一項。也只是回答出了這一項。後來面試官繼續問:還有其他方式嗎?這種方式的效率高嗎?複雜度是多少?
後來這場面試就失敗了,沒關係!痛定思痛繼續學習。
後來我知道了,indexOf需要把每個參數重新判斷一遍。效率很低下。而且indexOf還不能查到NaN
的索引值,如下代碼:
var arr = [1, NaN]
arr.indexOf(NaN) // -1
那如何解決這個查詢問題呢?我們下次談include和indexOf的時候細說!
我們先來驗證一下indexOf的效率問題?後面想想怎麼提高?
我們先準備兩個數組,一個長度爲:100000 一個:50000
var arr1 = Array.from(new Array(100000), (x, index)=>{
return index
})
var arr2 = Array.from(new Array(50000), (x, index)=>{
return index+index
})
var handleArray = arr1.concat(arr2)
console.log('原數組的長度',handleArray.length)
let start = new Date().getTime()
console.log('開始數組去重...')
function unique(array) {
var res = []
for(var i = 0; i < array.length; i++) {
if(res.indexOf(array[i]) === -1) {
res.push(array[i])
}
}
return res
}
console.log('去重後的長度', unique(handleArray).length)
let end = new Date().getTime()
console.log('耗時', end - start)
執行結果如下:
一個150000的數組,去重要7723毫秒。(該值存在波動,這是一個平均值,後續相同)
二、排序後去重(sort())
我們可以先對數組進行排序,將相似的值排到一起
function unique(array) {
var sortArr = array.concat().sort() //返回新數組
var res = [sortArr[0]]
for(var i=1; i < sortArr.length; i++) {
if(sortArr[i] !== sortArr[i-1]) {
res.push(sortArr[i])
}
}
return res
}
代碼解析:
- 1、我們先對數組進行排序,爲了不影響原數組,我們使用了concat()方法,返回新數組。
- 2、將排序好的數組進行相鄰位的比較。如果不相等,推到結果數組裏。相等跳過操作。
- 3、res = [sortArr[0]] 和 i = 1 是個初始化操作
這樣的做法,省去了對重複參數的索引。效率上面確實也提升不少。對於一些重複性比較高的數組進行去重,效率明顯提升。
我們看一下優化後的效果:
看到這個結果,嚇我一跳啊!竟然只要106毫秒。優化真的是一門學問啊!
三、利用雙層循環
上面的兩種方法都是使用了indexOf這個api,除了這個有沒有其他的呢?答案是肯定的。就是利用雙層循環
function unique(array){
var res = []
for(var i=0;i<array.length;i++) {
for(var j=0;j<res.length;j++) {
if(array[i] === res[j]) {
break
}
}
if(j === res.length) {
res.push(array[i])
}
}
return res
}
代碼解析:
- 1、對初始值數組進行每一項與目標數組每一項進行比較
- 2、如果有重複就跳出循環
- 3、如果全部都不相等,那麼j就會等於res.length。此時,把值添加到res中
這樣的方法,不失爲一種新解法。但是由於雙層循環的存在,其效率也不會改善多少。
我們看到相比方法一確實快了不少,但是效率還是比較低下的。不推薦大家使用!
四、利用ES6 new Set()
這個是es6的語法糖,極度簡單明瞭!效率也是槓槓滴!
function unique(array){
return Array.from(new Set(array)
// return [...new Set(array)]
}
這樣的方法,沒啥好說的。就是一個字快!
我們看看執行效果:
官方的優化總是不讓人失望,真的太牛了!爸爸始終是爸爸!
五、利用對象屬性不可重複(該方法有侷限性,不推薦使用)
function unique(array){
var obj = {}
var res = []
for(var i =0; i < array.length; i++) {
if(!obj[array[i]]) {
res.push(array[i])
obj[array[i]] = 1
} else {
obj[array[i]]++
}
}
return res
}
代碼解析:
- 1、申明一個對象,將數組裏每一項的值作爲對象的key
- 2、判斷obj.key值是否爲undefined?是!則目標數組將key作爲一個參數項添加進來。並給obj.key 一個默認值 1
- 3、obj.key不是undefined的話,則證明目標數組已經有key的參數項。對obj.key進行加1操作即可。
再來看一下:
看到這個圖的時候!我不禁多執行了幾次。但是事實告訴我:你沒有看錯,比new Set()還要高效。
重點說明一下:就是對象 1 和 ‘1’ 這樣的方式無法區分。需要引入typeof hasOwnProperty等操作。這裏就不展開討論了。
總結
在寫去重之前,我也看了網上不少博文。說多少種的都有。其實感覺就是使用了不同的語法糖進行了循環操作和刪除操作。比如 filter reduce for of include splice
Map等方法。思路和想法確實很新穎。我把鏈接放到了下面。還有我自己的測試代碼,如需自取。
正在讀此文的朋友,如果明天面試官問你這樣的問題,你是不是跟我一樣的心態呢?
源碼地址:
源碼
我的小站: https://shenzhiyong.com.cn
參考鏈接: