Lodash源码阅读-sortedLastIndexOf

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. 先检查数组是否为空,空的就直接返回 -1
  2. baseSortedIndex 函数找值应该插入的最高位置 (关键是传入第三个参数 true
  3. 把得到的索引减 1,就是最后一个可能等于目标值的位置
  4. 检查这个位置的值是否等于要找的值:
    • 是的话,返回这个位置
    • 不是的话,说明数组里没有这个值,返回 -1

这种方法比从后往前遍历要高效得多,特别是在大数组中查找时。

源码解析

边界检查

javascript 复制代码
var length = array == null ? 0 : array.length;
if (length) {
  // 查找逻辑
}
return -1;

先看数组是否为空:

  • 如果传入的 arraynullundefined,就把长度设为 0
  • 只有数组有内容时才进行查找
  • 如果数组是空的,直接返回 -1

查找和匹配

javascript 复制代码
var index = baseSortedIndex(array, value, true) - 1;
if (eq(array[index], value)) {
  return index;
}

这部分是核心逻辑:

  1. 调用 baseSortedIndex 并传入 true 作为第三个参数(这是重点!)
    • 这会找到值应该插入的最高位置,也就是所有等于该值的元素之后的那个位置
  2. 将索引减 1,得到最后一个可能匹配的元素位置
  3. eq 函数检查该位置的值是否等于要找的值
  4. 如果匹配,返回这个位置;否则返回 -1

注意这里与 sortedIndexOf 不同,没有 index < length 的判断,原因是:

  • sortedLastIndexOf 中,我们是从 baseSortedIndex 返回的索引减 1
  • 这个索引最大只可能是数组长度,减 1 后最大是 length - 1
  • 所以 index 一定小于 length,不需要额外判断
  • 而且如果值比数组中所有元素都小,baseSortedIndex 会返回 0,减 1 后变成 -1,此时访问 array[-1] 会返回 undefinedeq(undefined, value) 会返回 false,函数正确返回 -1

这种设计既简洁又安全,避免了不必要的条件检查。

与 sortedIndexOf 的区别

这个函数与 sortedIndexOf 非常相似,主要区别在于:

  • sortedIndexOf第一个匹配的位置
  • sortedLastIndexOf最后一个匹配的位置

实现上的区别就是在调用 baseSortedIndex 时,传入 true 作为第三个参数,以及后续处理索引的方式不同。

总结

sortedLastIndexOf 这个小函数再次展示了算法选择的重要性:

  1. 巧妙利用二分查找

    • 通过设置 retHighest 参数,可以找到值的最后位置而不是第一个位置
    • 将 O(n) 的线性查找优化为 O(log n) 的二分查找
  2. 代码复用的艺术

    • sortedIndexOf 共用核心的 baseSortedIndex 函数
    • 通过参数和简单的后处理,实现了完全不同的功能
  3. 设计上的一致性

    • 与 JavaScript 原生的 lastIndexOf 接口保持一致
    • 函数命名清晰地表明了它的用途和使用场景

sortedLastIndexOfsortedIndexOf 共同构成了处理已排序数组的高效工具,特别适合处理有重复元素的场景,是 Lodash 设计精巧的又一例证。

相关推荐
程序员爱钓鱼1 分钟前
Go语言泛型-泛型约束与实践
前端·后端·go
前端小巷子3 分钟前
web从输入网址到页面加载完成
前端·面试·浏览器
江城开朗的豌豆4 分钟前
Vue路由动态生成秘籍:让你的链接'活'起来!
前端·javascript·vue.js
晓得迷路了4 分钟前
栗子前端技术周刊第 88 期 - Apache ECharts 6.0 beta、Deno 2.4、Astro 5.11...
前端·javascript·echarts
江城开朗的豌豆10 分钟前
在写vue公用组件的时候,怎么提高可配置性
前端·javascript·vue.js
江城开朗的豌豆10 分钟前
Vue路由跳转的N种姿势,总有一种适合你!
前端·javascript·vue.js
江城开朗的豌豆11 分钟前
Vue路由玩法大揭秘:三种路由模式你Pick谁?
前端·javascript·vue.js
江城开朗的豌豆11 分钟前
Vue路由守卫全攻略:给页面访问装上'安检门'
前端·javascript·vue.js
小磊哥er18 分钟前
【前端工程化】前端组件模版构建那些事
前端
前端 贾公子19 分钟前
monorepo + Turborepo --- 开发应用程序
java·前端·javascript