數組去重是一個老生常談的話題,在面試中也常常會被問道。對於去重,有兩種主流思想:javascript
- 先排序,線性遍歷後去重,時間複雜度O(n*log2n);
- 使用哈希,空間換時間,時間複雜度O(n);
上一篇文章,我分析了underscore的函數是如何組織的,咱們可以依照這種方法書寫本身的函數庫,這篇文章,來看看關於函數去重underscore是如何作的?java
underscore的去重是指數組(Arrays)中uniq
函數,其API以下:面試
uniq_.uniq(array, [isSorted], [iteratee])
別名:unique
說明:返回 array去重後的副本, 使用 === 作相等測試. 若是您肯定 array 已經排序, 那麼給 isSorted 參數傳遞 true值, 此函數將運行的更快的算法. 若是要處理對象元素, 傳參 iterator 來獲取要對比的屬性.
上述API主要想說明幾點:算法
a===b
,代表不只要值相等,類型也須要相等uniq
也能夠比較對象,前提是須要指定比較的對象屬性 咱們簡單使用如下_.uniq(array, [isSorted], [iteratee])
,以下:數組
console.log(_.uniq([1,4,2,2,3,3])); console.log(_.uniq([1,2,2,2,3,3],true)); console.log(_.uniq([{ name:1, gender:"male" },{ name:2, gender:"female" },{ name:2, gender:"male" },{ name:4, gender:"male" }],true,"gender"));
結果以下:
閉包
underscore去重的核心思想:函數
新建 結果集數組res
,遍歷待 去重數組,將每一個遍歷值在res
數組中遍歷檢查,將不存在當前res
中的遍歷值壓入res
中,最後輸出res
數組。
function uniq(array){ var res = []; array.forEach(function(element) { if(res.indexOf(element)<0){ res.push(element); } }, this); return res; } console.log(uniq([1,4,2,2,3,3])); //[1,4,2,3]
其中若是數組是排序的,去重運算效率更高,由於排序可以將相同的數排列在一塊兒,方便先後比較。測試
function uniq(array, isSorted) { var res = []; var seen = null; array.forEach(function (element,index) { if (isSorted) { //當數組有序 if(!index || seen !== element) res.push(element); seen = element; } else { if (res.indexOf(element) < 0) { res.push(element); } } }, this); return res; } console.log(uniq([1,2,"2",3,3,3,5],true)); //(5) [1, 2, "2", 3, 5]
對於對象的去重,咱們知道{}==={}爲false
,因此使用===
比較對象在實際場景中沒有意義。
在這裏我舉個實際場景的例子:this
我要在小組中選擇一名男生(male)和一名女生(female),小組組員狀況以下:
var array = [{ name:"Tom", gender:"female" },{ name:"Lucy", gender:"female" },{ name:"Edward", gender:"male" },{ name:"Molly", gender:"female" }]
咱們修改上面的uniq
:spa
function uniq(array, isSorted, iteratee) { var res = []; var seen = []; array.forEach(function (element, index) { if (iteratee) { //判斷iteratee是否存在,存在的話,取出真正要比較的屬性 var computed = element[iteratee]; if (seen.indexOf(computed) < 0) { seen.push(computed); res.push(element); } } else if (isSorted) { //當數組有序 if (!index || seen !== element) res.push(element); seen = element; } else { if (res.indexOf(element) < 0) { res.push(element); } } }, this); return res; } console.log(uniq([{ name:"Tom", gender:"female" },{ name:"Lucy", gender:"female" },{ name:"Edward", gender:"male" },{ name:"Molly", gender:"female" }],true,"gender"));
結果以下:
underscore的uniq
的實現,基本上使用的上述思想。在附錄中我附上了源碼和一些註釋。
上述我分析了underscore的uniq
函數實現,在這以前我也看過諸如《JavaScript去重的N種方法》...之類的文章,underscore中的uniq
函數實現方法並非最優解,至少從時間複雜度來說不是最優。
那麼爲何underscore不用Set
對象來解決去重問題,使用indexof
查找的時間複雜度是O(n)
,而hash查詢是O(1)
。
我我的認爲Set
是ES6中引的對象,underscore是爲了考慮兼容性問題。
那爲何不用obj
做爲Set
的替代方案呢?
這裏我猜是underscore的設計者只想用本身內部實現的_.indexOf
函數。此處是個人猜想,你們若是有想法,歡迎你們留言!
下面我附上ES6的實現(你們最熟悉的):
var a = [1,1,2,3,4,4]; var res = [...new Set(a)];
再附上obj
的實現:
function uniq(array,iteratee){ var res = []; var obj = {}; array.forEach(function(element) { var computed = element; if(iteratee) computed = element[iteratee]; if(!obj.hasOwnProperty(computed)) obj[(typeof computed)+"_"+JSON.stringify(computed)] = element; }, this); for(var p in obj){ res.push(obj[p]); } return res; } uniq([1,"1",2,3,4,4]);// (5) [1, "1", 2, 3, 4]
underscore的uniq
函數源碼及註釋:
_.uniq = _.unique = function(array, isSorted, iteratee, context) { if (array == null) return []; if (!_.isBoolean(isSorted)) { //若是沒有排序 context = iteratee; iteratee = isSorted; isSorted = false; } /** ** 此處_.iteratee ** function (key){ * return function(obj){ * return obj[key]; * } ** } ** key就是這裏的iteratee(對象的屬性),這裏使用了閉包 **/ if (iteratee != null) iteratee = _.iteratee(iteratee, context); var result = [];//返回去重後的數組(副本) var seen = []; for (var i = 0, length = array.length; i < length; i++) { var value = array[i];//當前比較值 if (isSorted) { //若是i=0時,或者seen(上一個值)不等於當前值,放入去重數組中 if (!i || seen !== value) result.push(value); seen = value;//保存當前值,用於下一次比較 } else if (iteratee) { var computed = iteratee(value, i, array); if (_.indexOf(seen, computed) < 0) { seen.push(computed); result.push(value); } } else if (_.indexOf(result, value) < 0) { result.push(value); } } return result; };