学underscore在数组中查找指定元素
前言
在开发中,我们经常会遇到在数组中查找指定元素的需求,可能大家觉得这个需求过于简单,然而如何优雅的去实现一个 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 并不支持。
学underscore在数组中查找指定元素的更多相关文章
- Java-Runoob-高级教程-实例-数组:14. Java 实例 – 在数组中查找指定元素
ylbtech-Java-Runoob-高级教程-实例-数组:14. Java 实例 – 在数组中查找指定元素 1.返回顶部 1. Java 实例 - 在数组中查找指定元素 Java 实例 以下实例 ...
- 对于一个有序数组,我们通常采用二分查找的方式来定位某一元素,请编写二分查找的算法,在数组中查找指定元素。 给定一个整数数组A及它的大小n,同时给定要查找的元素val,请返回它在数组中的位置(从0开始),若不存在该元素,返回-1。若该元素出现多次,请返回第一次出现的位置。
// ConsoleApplication10.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream& ...
- Java如何从数组中查找对象元素?
在Java中,如何从数组中查找对象元素? 示例 以下示例使用Contains方法来搜索数组中的String对象. package com.yiibai; import java.util.*; pub ...
- ES6数组中删除指定元素
知识点: ES6从数组中删除指定元素 findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引.否则返回-1. arr.splice(arr.findIndex(item => ...
- 【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法
Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...
- PHP从数组中找到指定元素的位置
群里有人问,有个数组五个元素 分为1到5 现在要求 循环找出3元素的索引,怎么做性能才是最高. 我不知道哪个性能最高,但是我想提出可以用多种方式进行查找,然后进行比较选择. 我想,最简单最基础的 应 ...
- 剑指offer-特定二维数组中查找一个元素是否存在-二分搜索-二维数组
int [][] array ={ {1,2,8,9}, {2,4,9,12}, {4,7,10,13}, {6,8,11,19} }; 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都 ...
- C# 移除string[] 数组中的指定元素
本文转载自 http://www.cnblogs.com/jcdd-4041/p/3279531.html 第一步:先把string[] 转换成 ArrayList 第二步:移除指定元素 第三步 ...
- jq从数组中删除指定元素(根据自定义条件) 超好用的 $.grep() 方法
转: jQuery.grep() 什么是jQuery.grep()? jQuery.grep()是一个查找满足过滤函数的数组元素的函数.原始数组不受影响,返回值为数组. 用法介绍: 写法: jQuer ...
随机推荐
- 探索免费开源服务器tomcat的魅力
Tomcat最初是由Sun的软件架构师詹姆斯·邓肯·戴维森开发的.后来他帮助将其变为开源项目,并由Sun贡献给Apache软件基金会,并且成为Jakarta 项目中的一个核心项目.因此逐渐成为世界上广 ...
- Kafka压测— 搞垮kafka的方法(转)
分布式系统故障场景梳理方法: 场景梳理逻辑关系: 单点硬件故障→单点进程故障类型→集群影响→集群故障场景 第三方依赖故障→集群依赖关系→集群影响→集群故障场景 业务场景→集群负载/错误影响→集群故障场 ...
- SpringBoot项目使用RedisTemplate设置序列化方式
前端时间新项目使用SpringBoot的RedisTemplate遇到一个问题,先简单描述一下问题:不同项目之间redis共用一个,但是我们新项目读不到老项目存储的缓存.新项目搭建的时候没有跟老项目使 ...
- 深入浅出JVM(一):运行时数据区域
程序计数器 线程私有 指向了正在执行的虚拟机字节码指令的地址:如果是本地方法,数值为空 没有 OutOfMemoryError 错误的区域 Java虚拟机栈 线程私有: 生命周期与线程相同: 代表着 ...
- c++无关类型指针的强制转换原理的分析和尝试
因最近看到大量的c类型指针强制转换,联系到c++的reinterpret_cast强制转换符,故总结一下. 先上图 由图中可以看出,先声明了一个结构体t与一个含有三个元素的数组num,接着声明一个指向 ...
- Educational Codeforces Round 71
https://www.cnblogs.com/31415926535x/p/11460682.html 上午没课,做一套题,,练一下手感和思维,,教育场的71 ,,前两到没啥,,后面就做的磕磕巴巴的 ...
- 登陆并访问k8s的apiserver
kubeadm安装的k8s集群默认需要用户登陆认证,无法直接使用命令curl访问.所以首先的第一步就是获取token. 先找到k8s集群中的dns组件coredns,之前的版本使用的是kube-dns ...
- leetcode整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123输出: 321 示例 2: 输入: -123输出: -321 示例 3: 输入: 120输出: 21 ...
- Java -- 最简单的认识重载
定义 方法的名称相同,参数个数或类型不同的时候就成为方法重载. 示例 编写一个两个数相加的方法: public class hello{ public static void main(String ...
- 创建包含CRUD操作的Web API接口-第一部
在这里,我们将创建一个新的Web API项目,它将使用实体框架实现Get,POST.PUT和DELETE方法来实现CRUD操作. 首先,在Visual Studio 2013 for Web expr ...