學underscore在數組中查找指定元素

前言

在開發中,咱們常常會遇到在數組中查找指定元素的需求,可能你們以爲這個需求過於簡單,然而如何優雅的去實現一個 findIndex 和 findLastIndex、indexOf 和 lastIndexOf 方法倒是不多人去思考的。本文就帶着你們一塊兒參考着 underscore 去實現這些方法。面試

在實現前,先看看 ES6 的 findIndex 方法,讓你們瞭解 findIndex 的使用方法。數組

findIndex

ES6 對數組新增了 findIndex 方法,它會返回數組中知足提供的函數的第一個元素的索引,不然返回 -1。函數

舉個例子:學習

function isBigEnough(element) { return element >= 15; } [12, 5, 8, 130, 44].findIndex(isBigEnough); // 3

findIndex 會找出第一個大於 15 的元素的下標,因此最後返回 3。優化

是否是很簡單,其實,咱們本身去實現一個 findIndex 也很簡單。this

實現findIndex

思路天然很明瞭,遍歷一遍,返回符合要求的值的下標便可。spa

function findIndex(array, predicate, context) { for (var i = 0; i < array.length; i++) { if (predicate.call(context, array[i], i, array)) return i; } return -1; } console.log(findIndex([1, 2, 3, 4], function(item, i, array){ if (item == 3) return true; })) // 2

findLastIndex

findIndex 是正序查找,但正如 indexOf 還有一個對應的 lastIndexOf 方法,咱們也想寫一個倒序查找的 findLastIndex 函數。實現天然也很簡單,只要修改下循環便可。索引

function findLastIndex(array, predicate, context) { var length = array.length; for (var i = length; i >= 0; i--) { if (predicate.call(context, array[i], i, array)) return i; } return -1; } console.log(findLastIndex([1, 2, 3, 4], function(item, index, array){ if (item == 1) return true; })) // 0

createIndexFinder

然而問題在於,findIndex 和 findLastIndex 其實有不少重複的部分,如何精簡冗餘的內容呢?這即是咱們要學習的地方,往後面試問到此類問題,也是加分的選項。element

underscore 的思路就是利用傳參的不一樣,返回不一樣的函數。這個天然是簡單,可是如何根據參數的不一樣,在同一個循環中,實現正序和倒序遍歷呢?開發

讓咱們直接模仿 underscore 的實現:

function createIndexFinder(dir) { return function(array, predicate, context) { var length = array.length; var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate.call(context, array[index], index, array)) return index; } return -1; } } var findIndex = createIndexFinder(1); var findLastIndex = createIndexFinder(-1);

sortedIndex

findIndex 和 findLastIndex 的需求算是結束了,可是又來了一個新需求:在一個排好序的數組中找到 value 對應的位置,保證插入數組後,依然保持有序的狀態。

假設該函數命名爲 sortedIndex,效果爲:

sortedIndex([10, 20, 30], 25); // 2

也就是說若是,注意是若是,25 按照此下標插入數組後,數組變成 [10, 20, 25, 30],數組依然是有序的狀態。

那麼這個又該如何實現呢?

既然是有序的數組,那咱們就不須要遍歷,大可使用二分查找法,肯定值的位置。讓咱們嘗試着去寫一版:

// 初版 function sortedIndex(array, obj) { var low = 0, high = array.length; while (low < high) { var mid = Math.floor((low + high) / 2); if (array[mid] < obj) low = mid + 1; else high = mid; } return high; }; console.log(sortedIndex([10, 20, 30, 40, 50], 35)) // 3

如今的方法雖然能用,但通用性不夠,好比咱們但願能處理這樣的狀況:

// stooges 配角 好比 三個臭皮匠 The Three Stooges var stooges = [{name: 'stooge1', age: 10}, {name: 'stooge2', age: 30}]; var result = sortedIndex(stooges, {name: 'stooge3', age: 20}, function(stooge){ return stooge.age }); console.log(result) // 1

因此咱們還須要再加上一個參數 iteratee 函數對數組的每個元素進行處理,通常這個時候,還會涉及到 this 指向的問題,因此咱們再傳一個 context 來讓咱們能夠指定 this,那麼這樣一個函數又該如何寫呢?

// 第二版 function cb(fn, context) { return function(obj) { return fn ? fn.call(context, obj) : obj; } } function sortedIndex(array, obj, iteratee, context) { iteratee = cb(iteratee, context) var low = 0, high = array.length; while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < iteratee(obj)) low = mid + 1; else high = mid; } return high; };

indexOf

sortedIndex 也完成了,如今咱們嘗試着去寫一個 indexOf 和 lastIndexOf 函數,學習 findIndex 和 FindLastIndex 的方式,咱們寫一版:

// 初版 function createIndexOfFinder(dir) { return function(array, item){ var length = array.length; var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (array[index] === item) return index; } return -1; } } var indexOf = createIndexOfFinder(1); var lastIndexOf = createIndexOfFinder(-1); var result = indexOf([1, 2, 3, 4, 5], 2); console.log(result) // 1

fromIndex

可是即便是數組的 indexOf 方法也能夠多傳遞一個參數 fromIndex,從 MDN 中看到 fromIndex 的講究可有點多:

設定開始查找的位置。若是該索引值大於或等於數組長度,意味着不會在數組裏查找,返回 -1。若是參數中提供的索引值是一個負值,則將其做爲數組末尾的一個抵消,即 -1 表示從最後一個元素開始查找,-2 表示從倒數第二個元素開始查找 ,以此類推。 注意:若是參數中提供的索引值是一個負值,仍然從前向後查詢數組。若是抵消後的索引值仍小於 0,則整個數組都將會被查詢。其默認值爲 0。

再看看 lastIndexOf 的 fromIndex:

今後位置開始逆向查找。默認爲數組的長度減 1,即整個數組都被查找。若是該值大於或等於數組的長度,則整個數組會被查找。若是爲負值,將其視爲從數組末尾向前的偏移。即便該值爲負,數組仍然會被從後向前查找。若是該值爲負時,其絕對值大於數組長度,則方法返回 -1,即數組不會被查找。

按照這麼多的規則,咱們嘗試着去寫第二版:

// 第二版 function createIndexOfFinder(dir) { return function(array, item, idx){ var length = array.length; var i = 0; if (typeof idx == "number") { if (dir > 0) { i = idx >= 0 ? idx : Math.max(length + idx, 0); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; } } var indexOf = createIndexOfFinder(1); var lastIndexOf = createIndexOfFinder(-1);

優化

到此爲止,已經很接近原生的 indexOf 函數了,可是 underscore 在此基礎上還作了兩點優化。

第一個優化是支持查找 NaN。

由於 NaN 不全等於 NaN,因此原生的 indexOf 並不能找出 NaN 的下標。

[1, NaN].indexOf(NaN) // -1

那麼咱們該如何實現這個功能呢?

就是從數組中找到符合條件的值的下標嘛,不就是咱們最一開始寫的 findIndex 嗎?

咱們來寫一下:

// 第三版 function createIndexOfFinder(dir, predicate) { return function(array, item, idx){ if () { ... } // 判斷元素是不是 NaN if (item !== item) { // 在截取好的數組中查找第一個知足isNaN函數的元素的下標 idx = predicate(array.slice(i, length), isNaN) return idx >= 0 ? idx + i: -1; } for () { ... } } } var indexOf = createIndexOfFinder(1, findIndex); var lastIndexOf = createIndexOfFinder(-1, findLastIndex);

第二個優化是支持對有序的數組進行更快的二分查找。

若是 indexOf 第三個參數不傳開始搜索的下標值,而是一個布爾值 true,就認爲數組是一個排好序的數組,這時候,就會採用更快的二分法進行查找,這個時候,能夠利用咱們寫的 sortedIndex 函數。

在這裏直接給最終的源碼:

// 第四版 function createIndexOfFinder(dir, predicate, sortedIndex) { return function(array, item, idx){ var length = array.length; var i = 0; if (typeof idx == "number") { if (dir > 0) { i = idx >= 0 ? idx : Math.max(length + idx, 0); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } else if (sortedIndex && idx && length) { idx = sortedIndex(array, item); // 若是該插入的位置的值正好等於元素的值,說明是第一個符合要求的值 return array[idx] === item ? idx : -1; } // 判斷是不是 NaN if (item !== item) { idx = predicate(array.slice(i, length), isNaN) return idx >= 0 ? idx + i: -1; } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; } } var indexOf = createIndexOfFinder(1, findIndex, sortedIndex); var lastIndexOf = createIndexOfFinder(-1, findLastIndex);

值得注意的是:在 underscore 的實現中,只有 indexOf 是支持有序數組使用二分查找,lastIndexOf 並不支持。

相關文章
相關標籤/搜索