JS查找数组指定元素

查找数组指定元素

ES6 findIndex

findIndex它会返回数组中满足提供的函数的第一个元素的索引,否则返回 -1

    let arr = [1,15,15,45,24,37,298,111,15]
    arr.findIndex(isBigEnough)

    isBigEnough(element){
        return element > 200
    },

模拟实现findIndex

    findIndex(arr,func,context){
        for(let i =0;i<arr.length;i++){
            if (func.call(context, arr[i])) return i;
        }
        return -1
    },

模拟实现findLastIndex

跟findIndex差不多,只不过是倒序查找

    findLastIndex(arr,func,context){
        var length = arr.length
        for(let i = length-1 ;i>=0;i--){
            if(func.call(context,arr[i])) return i
        }
        return -1
    },

createIndexFinder

观察可以知道findIndex 和findLastIndex的实现原理基本相同,区别只在于查找顺讯,那么如何通过实现一个函数,通过不同的传参来判断是使用findIndex还是findLastIndex呢?

实现:

    createIndexFinder(dir){
        return function(arr,func,context){
            let length = arr.length;
            let index = dir > 0 ? 0 : length -1
            for(;index >=0 && index < length; index+=dir){
                if(func.call(context,arr[index])) return index
            }
            return -1
        }
    },

sortedIndex

新的需求:如何在一个排好序的数组中找到 value 对应的位置,保证插入数组后,依然保持有序的状态?

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

如果是有序数组,那我们就不采用遍历的形式,采用二分法

    sortedIndex(arr,target){
        let low = 0,high = arr.length;
        while(low<high){
            let mid = Math.floor((high+low)/2)
            if(arr[mid]<target){
                low = mid + 1
            }else{
                high = mid
            }
        }
        return high
    },

看上去不错,基本实现了我们的要求,但还不够好,如果我想实现下面这种情况要怎么处理?

// 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

进阶实现:

    sortedIndex(arr,target,iteratee,context){
        iteratee = this.cb(iteratee,context)
        let low = 0,high = arr.length;
        while(low < high){
            let mid = Math.floor((low + high) / 2)
            if(iteratee(arr[mid]) < iteratee(target)){
                low = mid + 1
            }else{
                high = mid
            }
        }
        return high
    },
    // 处理函数this问题
    cb(func,context){
        if(context === void 0){
            return func
        }else{
            return func.apply(context,arguments)
        }
    },

indexOf/lastIndexOf

现在尝试手写一个indexOf/lastIndexOf

    createIndexOfFinder(dir){
        return function(arr,item){
            let length = arr.length
            let index = dir > 0 ? 0 : length - 1
            for(;index>= 0 && index<length;index+=dir){
                if(arr[index] === item){
                    return index
                }
            }
            return -1
        }
    },
    var indexOf = createIndexOfFinder(1);
    var lastIndexOf = createIndexOfFinder(-1);

indexOf和lastIndexOf都支持第二个参数fromIndex表示开始查找的位置。

在MDN上对fromIndex的解释如下:

  • indexOf:

fromIndex

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

比如:

    var array = [2, 5, 9,11,13];
    array.indexOf(2, 3); // -1  表示从第三个位置开始向后查找
    array.indexOf(5, -4) // 1  -4表示从倒数第四个位置开始向后查找
  • lastIndexOf

fromIndex

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

    var array = [2, 5, 9,2,11,13];
    array.lastIndexOf(2,2) // 0 从第二个位置向前查找 
    array.lastIndexOf(2,-3) // 3 从后向前数三个位置开始向前查找
    

根据以上规则,我们实现第二版

    createIndexOfFinder(dir){
        return function(arr,item,idx){
            let length = arr.length
            let i=0
            if(typeof idx == 'number'){
                if(dir > 0){
                    i = idx > 0 ? idx : Math.max(idx,length)
                }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(arr[idx] === item){
                    return idx
                }
            }
            return -1
        }
    },

优化

主要围绕下面两点进行

  1. 如何支持查找NaN
  2. 支持对有序数组更快的二分查找,如果formIndex传一个true就表示数组有序

根据以上要求,看下最终实现方法

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 并不支持
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容