Lodash 源码阅读-sortedLastIndexOf
概述
sortedLastIndexOf
是 Lodash 里的一个查找函数,它在已排序的数组中找出某个值最后一次出现的位置。与普通的 lastIndexOf
相比,它利用数组已排好序的特点,用二分查找的方式快速定位,大大提高了搜索效率(时间复杂度从 O(n) 降到 O(log n))。
这个函数特别适合处理有重复元素的已排序数组。比如在 [1, 2, 3, 3, 3, 4]
这样的数组中,查找 3 的最后一个位置,普通的 lastIndexOf
需要从后往前遍历,而 sortedLastIndexOf
能快速定位到索引 4。
前置学习
依赖函数
- baseSortedIndex :内部的二分查找函数,通过设置
retHighest
参数为true
可以找到值应该插入的最高位置 - eq:判断两个值是否相等的函数,能正确处理各种特殊情况(比如 NaN)
技术知识
- 二分查找:在有序数据中高效查找的算法,每次把范围缩小一半
- 相等性比较:JavaScript 中判断值相等的方法和陷阱
- 边界检查:处理空数组和特殊输入值的技巧
源码实现
javascript
/**
* This method is like `_.lastIndexOf` except that it performs a binary
* search on a sorted `array`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Array
* @param {Array} array The array to inspect.
* @param {*} value The value to search for.
* @returns {number} Returns the index of the matched value, else `-1`.
* @example
*
* _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
* // => 3
*/
function sortedLastIndexOf(array, value) {
var length = array == null ? 0 : array.length;
if (length) {
var index = baseSortedIndex(array, value, true) - 1;
if (eq(array[index], value)) {
return index;
}
}
return -1;
}
实现思路
sortedLastIndexOf
的思路很巧妙:
- 先检查数组是否为空,空的就直接返回 -1
- 用
baseSortedIndex
函数找值应该插入的最高位置 (关键是传入第三个参数true
) - 把得到的索引减 1,就是最后一个可能等于目标值的位置
- 检查这个位置的值是否等于要找的值:
- 是的话,返回这个位置
- 不是的话,说明数组里没有这个值,返回 -1
这种方法比从后往前遍历要高效得多,特别是在大数组中查找时。
源码解析
边界检查
javascript
var length = array == null ? 0 : array.length;
if (length) {
// 查找逻辑
}
return -1;
先看数组是否为空:
- 如果传入的
array
是null
或undefined
,就把长度设为 0 - 只有数组有内容时才进行查找
- 如果数组是空的,直接返回 -1
查找和匹配
javascript
var index = baseSortedIndex(array, value, true) - 1;
if (eq(array[index], value)) {
return index;
}
这部分是核心逻辑:
- 调用
baseSortedIndex
并传入true
作为第三个参数(这是重点!)- 这会找到值应该插入的最高位置,也就是所有等于该值的元素之后的那个位置
- 将索引减 1,得到最后一个可能匹配的元素位置
- 用
eq
函数检查该位置的值是否等于要找的值 - 如果匹配,返回这个位置;否则返回 -1
注意这里与 sortedIndexOf
不同,没有 index < length
的判断,原因是:
- 在
sortedLastIndexOf
中,我们是从baseSortedIndex
返回的索引减 1 - 这个索引最大只可能是数组长度,减 1 后最大是
length - 1
- 所以
index
一定小于length
,不需要额外判断 - 而且如果值比数组中所有元素都小,
baseSortedIndex
会返回 0,减 1 后变成 -1,此时访问array[-1]
会返回undefined
,eq(undefined, value)
会返回false
,函数正确返回 -1
这种设计既简洁又安全,避免了不必要的条件检查。
与 sortedIndexOf 的区别
这个函数与 sortedIndexOf
非常相似,主要区别在于:
sortedIndexOf
找第一个匹配的位置sortedLastIndexOf
找最后一个匹配的位置
实现上的区别就是在调用 baseSortedIndex
时,传入 true
作为第三个参数,以及后续处理索引的方式不同。
总结
sortedLastIndexOf
这个小函数再次展示了算法选择的重要性:
-
巧妙利用二分查找
- 通过设置
retHighest
参数,可以找到值的最后位置而不是第一个位置 - 将 O(n) 的线性查找优化为 O(log n) 的二分查找
- 通过设置
-
代码复用的艺术
- 与
sortedIndexOf
共用核心的baseSortedIndex
函数 - 通过参数和简单的后处理,实现了完全不同的功能
- 与
-
设计上的一致性
- 与 JavaScript 原生的
lastIndexOf
接口保持一致 - 函数命名清晰地表明了它的用途和使用场景
- 与 JavaScript 原生的
sortedLastIndexOf
和 sortedIndexOf
共同构成了处理已排序数组的高效工具,特别适合处理有重复元素的场景,是 Lodash 设计精巧的又一例证。