es6 的 findIndex

es6 对数组新增了 findIndex 方法,返回数组中满足提供的函数的第一个元素的索引,否则返回 -1。

例子

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

实现 findIndex

遍历一次,返回符合条件的第一个元素的索引值。

  1. function findIndex(array, predicate, context) {
  2. for (let i = 0; i < array.length; i++) {
  3. if (predicate.call(context, array[i], i, array)) return i;
  4. }
  5. return -1;
  6. }

实现 findLastIndex

findIndex 是正序,那怎样实现倒序查找了?修改一下循环就可以了。

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

合并 findIndex 和 findLastIndex

上面的两个实现,代码重复太多了,那怎样合并到一起了?很简单利用传参的不同,返回不同的函数。

  1. // 根据传参的不同返回不同的函数
  2. function createIndexFinder(dir) {
  3. return function (array, predicate, context) {
  4. let length = array.length;
  5. let index = dir > 0 ? 0 : length - 1;
  6. // index >= 0 是倒序的临界条件;index < length 是正序的临界条件
  7. for (; index >= 0 && index < length; index += dir) {
  8. if (predicate.call(context, array[index], index, array)) return index;
  9. }
  10. return -1;
  11. };
  12. }
  13. const findIndex = createIndexFinder(1);
  14. const findLastIndex = createIndexFinder(-1);

sortedIndex

现在有个新需求:在一个排好序的数组中找到 value 对应的位置,保证插入到数组后,依然是有序的。

例子(假设有个叫 sortedIndex 的函数)

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

数组变成 [10, 20, 25, 30] 依然是有序的。

可以利用二分法来实现。

  1. function sortedIndex(array, obj) {
  2. let low = 0, high = array.length;
  3. while (low < high) {
  4. let mid = Math.floor((low + high) / 2);
  5. if (array[mid] < obj) low = mid + 1;
  6. else high = mid;
  7. }
  8. return high;
  9. }
  10. console.log(sortedIndex([10, 20, 30, 40, 50], 35)); // 3

支持更多情况,比如添加一个判断函数。

  1. function cb(func, context) {
  2. if (context === void 0) return func;
  3. return function () {
  4. return func.apply(context, arguments);
  5. };
  6. }
  7. function sortedIndexTwo(array, obj, iteratee, context) {
  8. iteratee = cb(iteratee, context);
  9. let low = 0, high = array.length;
  10. while (low < high) {
  11. let mid = Math.floor((low + high) / 2);
  12. if (iteratee(array[mid]) < iteratee(obj)) low = mid + 1;
  13. else high = mid;
  14. }
  15. return high;
  16. }
  17. const stooges = [{name: 'stooge1', age: 10}, {name: 'stooge2', age: 30}];
  18. const result = sortedIndexTwo(stooges, {name: 'stooge3', age: 20}, function(stooge){
  19. return stooge.age
  20. });
  21. console.log(result) // 1

实现 indexOflastIndexOf

也很简单,直接遍历。

  1. function createIndexOfFinder(dir) {
  2. return function (array, item) {
  3. let length = array.length;
  4. let index = dir > 0 ? 0 : length - 1;
  5. for (; index >= 0 && index < length; index += dir) {
  6. if (array[index] === item) return index;
  7. }
  8. return -1;
  9. };
  10. }
  11. const indexOf = createIndexOfFinder(1);
  12. const lastIndexOf = createIndexOfFinder(-1);
  13. const result = indexOf([1, 2, 3, 4, 5], 2);
  14. console.log(result); // 1

indexOf 和 lastIndexOf 都支持 fromIndex 参数,来看看 mdn 是怎样解释的。

indexOf

开始查找的位置。如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回-1。如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即-1表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找 ,以此类推。 注意:如果参数中提供的索引值是一个负值,并不改变其查找顺序,查找顺序仍然是从前向后查询数组。如果抵消后的索引值仍小于0,则整个数组都将会被查询。其默认值为0。

fromIndex
从此位置开始逆向查找。默认为数组的长度减 1(arr.length - 1),即整个数组都被查找。如果该值大于或等于数组的长度,则整个数组会被查找。如果为负值,将其视为从数组末尾向前的偏移。即使该值为负,数组仍然会被从后向前查找。如果该值为负时,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。

实现一下

  1. function createIndexOfFinder(dir) {
  2. return function (array, item, idx) {
  3. let length = array.length;
  4. let i = 0;
  5. if (typeof idx == "number") {
  6. if (dir > 0) {
  7. // 正序
  8. i = idx >= 0 ? idx : Math.max(length + idx, 0);
  9. } else {
  10. // 倒序
  11. // 这里的 + 1 是兼容下面 for 循环中第二个 length。
  12. // 这里的 + 1 在下面的 for 中第一个 length 也减去。
  13. length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
  14. }
  15. }
  16. for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
  17. if (array[idx] === item) return idx;
  18. }
  19. return -1;
  20. };
  21. }

支持查找 NaN

  1. function createIndexOfFinder(dir, predicate) {
  2. return function(array, item, idx){
  3. if () { ... };
  4. // 判断元素是否是 NaN
  5. if (item !== item) {
  6. // 在截取好的数组中查找第一个满足isNaN函数的元素的下标
  7. idx = predicate(array.slice(i, length), isNaN)
  8. return idx >= 0 ? idx + i: -1;
  9. }
  10. for () { ... };
  11. }
  12. }
  13. const indexOf = createIndexOfFinder(1, findIndex);
  14. const lastIndexOf = createIndexOfFinder(-1, findLastIndex);

对有序的数组使用二分查找。

如果 indexOf 的第三个参数不传开始搜索的下标值,而是一个 true,就认为数组是一个排好序的数组。就使用二分查找。我们可以利用 sortedIndex。

注意:倒序不支持。

最终代码

  1. function createIndexOfFinder(dir, predicate, sortedIndex) {
  2. return function (array, item, idx) {
  3. let length = array.length;
  4. let i = 0;
  5. if (typeof idx == "number") {
  6. if (dir > 0) {
  7. // 正序
  8. i = idx >= 0 ? idx : Math.max(length + idx, 0);
  9. } else {
  10. // 倒序
  11. // 这里的 + 1 是兼容下面 for 循环中第二个 length。
  12. // 这里的 + 1 在下面的 for 中第一个 length 也减去。
  13. length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
  14. }
  15. } else if (sortedIndex && idx && length) {
  16. idx = sortedIndex(array, item);
  17. // 如果该插入的位置的值正好等于元素的值,说明是第一个符合要求的值
  18. return array[idx] === item ? idx : -1;
  19. }
  20. // 判断是否是 NaN
  21. if (item !== item) {
  22. idx = predicate(array.slice(i, length), isNaN);
  23. return idx >= 0 ? idx + i : -1;
  24. }
  25. for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
  26. if (array[idx] === item) return idx;
  27. }
  28. return -1;
  29. };
  30. }
  31. const indexOf = createIndexOfFinder(1, findIndex, sortedIndex);
  32. const lastIndexOf = createIndexOfFinder(-1, findLastIndex);
  33. console.log(indexOf([NaN, 1, 3, 4], NaN));

参考:

[1] JavaScript专题之学underscore在数组中查找指定元素
[2] indexOf
[3] lastIndexOf
[4] findIndex
[5] isNaN