JavaScript专题之学underscore在数组中查找指定元素

JavaScript专题系列第十篇,解说怎样从数组中查找指定元素,而且随着 undersocre 完成 findIndex 和 findLastIndex、sortedIndex、indexOf 和 lastIndexOf

媒介

在开辟中,我们经常会碰到在数组中查找指定元素的需求,能够人人以为这个需求过于简朴,然则怎样文雅的去完成一个 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 也很简朴。

完成findIndex

思绪天然很清楚明了,遍历一遍,返回相符要求的值的下标即可。

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 实在有许多反复的部份,怎样精简冗余的内容呢?这便是我们要进修的处所,往后口试问到此类题目,也是加分的选项。

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 并不支撑。

    原文作者:冴羽
    原文地址: https://segmentfault.com/a/1190000010356043
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞