最近看一些面試題,不少都提到了數組去重,用的最多的不外乎就是下面這個例子node
arr.filter(function(value,index,arr){
return arr.indexOf(value,index+1) === -1
})面試
若是忽略其餘因素,只從代碼簡潔度和易讀性來講,這個代碼確實是很好的,也確實體現了對js的掌握程度。數組
可是,從其餘方面來講,這個代碼是否就真的是很好的呢?做爲一個曾經的半吊子acmer,效率恐怕是我最在乎的東西了。那咱們就來看下效率吧。瀏覽器
如下全部實驗均基於nodejs環境。dom
首先,咱們須要隨機生成一個足夠大的數組。這裏就來個10萬吧。而後跨度是1-10000。(爲何要提跨度,這個後面再說),而後記錄下開始時間,並使用上面的代碼跑一下,最後記錄結束時間,並相減就能夠了。測試
下面是代碼spa
1 var arr = []; 2 //生成隨機數組 3 for (var i = 100000; i >= 0; i--) { 4 arr.push(Math.ceil(Math.random()*10000)); 5 } 6 7 //開始時間 8 var now1 = new Date(); 9 var arr1 = arr.filter(function(value,index,arr){ 10 return arr.indexOf(value,index+1) === -1 11 }) 12 // console.log(arr1.sort(function(a,b){ 13 // return a-b; 14 // })); 15 //結束時間 16 var now2 = new Date(); 17 console.log(now2-now1);
那麼10萬個跨度是10000的數組,效率是多少呢?code
執行結果: 2000毫秒左右,3000毫秒之內(其餘瀏覽器環境測試結果可能不盡相同)對象
這個效率,恩。。很通常。blog
咱們看下代碼也不可貴出這個結論。filter效率爲n,indexOf效率爲n,又是嵌套的,那麼效率就是n²了。(這裏有小問題,下面會解答)
接下來是我本身想的兩個去重的方法,對應不一樣的場景吧。
一:若是數組裏面的值都是數字的話。那麼,咱們可使用數字對應下標的方法來進行去重。好比說,個人數組是arr1=[1,3,3,3,5,6],那麼我就新開一個數組arr2,arr2的值就是[1,,1,,1,1]
也就是說,只要是arr1中出現過的值,我就在arr2中找到對應的下標,而且賦值爲1。
1 var now1 = new Date(); 2 3 //中間數組,用來記錄arr出現過的值 4 var tmp = []; 5 arr.forEach(function(value){ 6 tmp[value] = 1; 7 }); 8 9 var arr2 = []; 10 tmp.filter(function(value,index){ 11 if(value === 1){ 12 arr2.push(index); 13 } 14 }) 15 16 var now2 = new Date(); 17 console.log(now2-now1);
執行結果: 5毫秒左右,10毫秒之內
那麼這個結果就很明顯,效率之間的差距不是通常的大。。。
從代碼也能夠看出來,都只有一層循環,那麼效率就是O(n)了。仍是很是快的。
二:使用sort進行排序。並使用filter過濾數組便可
1 var now1 = new Date(); 2 //使用sort從小到大排序並過濾掉先後相同的元素 3 var arr3 = arr.sort(function(a,b){ 4 return a-b; 5 }).filter(function(value,index){ 6 return arr[index] !== arr[index-1]; 7 }); 8 var now2 = new Date(); 9 console.log(now2-now1);
執行結果: 80毫秒左右,100毫秒之內
能夠看出來這個結果也還算能夠(對比第一種)
不難看出來,node中sort的效率應該在nlogn(-。-廢話麼這不是)
而且2次遍歷數組的消耗基本能夠忽略不計(從第二種能夠看出來)
那麼爲何既然比第二個的效率低,又沒第一個簡潔,爲何要用這種方法呢,第一是能夠擴寬思路,第二是由於這種方法的適用範圍比第二種要高。
好比說,我要去重的數組不是數字,而是字符。那麼第二種方法就沒法使用(可使用對象來實現,不過不知道對象遍歷的效率高不高,留做之後測試)。而且, 第一種方法若是其中有一個數字爲10E,那麼空間消耗就很大(js中不肯定,大部分語言是這樣)。由於要開一個10E的數組。。。之前比賽的時候用c開個1000萬的數組就超了。
三個方法放一次看下:
1 var arr = []; 2 //生成隨機數組 3 for (var i = 100000; i >= 0; i--) { 4 arr.push(Math.ceil(Math.random()*10000)); 5 } 6 7 //開始時間 8 var now1 = new Date(); 9 var arr1 = arr.filter(function(value,index,arr){ 10 return arr.indexOf(value,index+1) === -1 11 }) 12 13 //結束時間 14 var now2 = new Date(); 15 console.log(now2-now1); 16 17 console.log("------------------------"); 18 19 now1 = new Date(); 20 21 //中間數組,用來記錄arr出現過的值 22 var tmp = []; 23 arr.forEach(function(value){ 24 tmp[value] = 1; 25 }); 26 27 var arr2 = []; 28 tmp.filter(function(value,index){ 29 if(value === 1){ 30 arr2.push(index); 31 } 32 }) 33 34 now2 = new Date(); 35 console.log(now2-now1); 36 37 console.log("------------------------"); 38 39 now1 = new Date(); 40 //使用sort從小到大排序並過濾掉先後相同的元素 41 arr3 = arr.sort(function(a,b){ 42 return a-b; 43 }).filter(function(value,index){ 44 return arr[index] !== arr[index-1]; 45 }); 46 //過濾數組 47 now2 = new Date(); 48 console.log(now2-now1);
效率對比就很是明顯了。
下面來講一下以前留下的坑:
一、爲何要用跨度呢,由於實驗中發現(一開始並無細想),若是數組跨度越下,那麼第一種方法的速度就越快,可是其餘兩種,尤爲是第二種的變化幅度就沒那麼劇烈,這是爲何呢?
實際上是由於,indexOf的機制形成的。indexOf的實現是從你規定的位置開始日後找,找到第一個就停下來。因此很明顯的,若是跨度越小,那麼出現重複數字的概率就越高,那麼就越有可能很快有返回結果,因此跨度越小就會越接近n的效率。
反觀其餘兩個方法,無論你跨度多少,影響的都是數組的長度,那麼影響就只在遍歷一遍數組的效率這方面,因此就很小。
二、其實第一種方法的2000毫秒左右的效率是基因而隨機數組的狀況下,那麼若是咱們把數組改爲順序數組,也就是沒有重複的數組呢?
咱們來試驗一下
var arr = []; //生成順序數組 for (var i = 100000; i >= 0; i--) { arr.push(i); }
看到了吧,這纔是真正的n²的效率,以前的2000毫秒和這個仍是有常數級別的區別的。
咱們還發現,第三種方法的速度也變久了。將近翻了一倍,這個的話應該是sort內部實現的問題,我也不知道node內部sort使用的是什麼排序,不過排序方法通常都是對順序或者倒序和隨機之間有必定的差距,這個很正常。
實驗作完了,咱們來總結一下:
第一種:
優勢:簡潔,明瞭,必定程度上能夠看出對js的掌握程度,適用範圍很廣,而且不會改變數組元素的相對位置(分爲對後去重和對前去重兩種)。
缺點:效率很是慢,尤爲是在重複數很是少的狀況下。
第二種:
優勢:速度很是快,受其餘因素影響小(若是數量少,跨度大的話,也就是稀疏數組,反而會比其餘的慢)。
缺點:適用範圍比較小,只能適合數字數組,而且數組不能過大。若是爲其餘數組,可使用對象做爲數組。遍歷使用for in 便可,效率和第三種差距不大。代碼以下,可自行實驗。
1 var now1 = new Date(); 2 3 //中間對象 4 var tmp = {}; 5 arr.forEach(function(value){ 6 tmp[value+'we'] = 1; 7 }) 8 9 var arr3 = []; 10 for(index in tmp){ 11 if(tmp[index] === 1){ 12 arr3.push(index); 13 } 14 } 15 16 var now2 = new Date(); 17 console.log(now2-now1);
第三種:
優勢:速度相對較快,受其餘因素影響不大,適用範圍也相對較廣。
缺點:中軌終於,適用範圍不如第一種大,速度不如第三種快。會受其餘因素必定的影響。