最近看一些面试题,很多都提到了数组去重,用的最多的不外乎就是下面这个例子

arr.filter(function(value,index,arr){
  return arr.indexOf(value,index+1) === -1
})

如果忽略其他因素,只从代码简洁度和易读性来说,这个代码确实是很好的,也确实体现了对js的掌握程度。

但是,从其他方面来说,这个代码是否就真的是很好的呢?作为一个曾经的半吊子acmer,效率恐怕是我最在意的东西了。那我们就来看下效率吧。

以下所有实验均基于nodejs环境。

首先,我们需要随机生成一个足够大的数组。这里就来个10万吧。然后跨度是1-10000。(为什么要提跨度,这个后面再说),然后记录下开始时间,并使用上面的代码跑一下,最后记录结束时间,并相减就可以了。

下面是代码

 var arr = [];
//生成随机数组
for (var i = 100000; i >= 0; i--) {
arr.push(Math.ceil(Math.random()*10000));
} //开始时间
var now1 = new Date();
var arr1 = arr.filter(function(value,index,arr){
return arr.indexOf(value,index+1) === -1
})
// console.log(arr1.sort(function(a,b){
// return a-b;
// }));
//结束时间
var now2 = new Date();
console.log(now2-now1);

那么10万个跨度是10000的数组,效率是多少呢?

执行结果: 2000毫秒左右,3000毫秒以内(其他浏览器环境测试结果可能不尽相同)

这个效率,恩。。很一般。

我们看下代码也不难得出这个结论。filter效率为n,indexOf效率为n,又是嵌套的,那么效率就是n²了。(这里有小问题,下面会解答)

接下来是我自己想的两个去重的方法,对应不同的场景吧。

一:如果数组里面的值都是数字的话。那么,我们可以使用数字对应下标的方法来进行去重。比如说,我的数组是arr1=[1,3,3,3,5,6],那么我就新开一个数组arr2,arr2的值就是[1,,1,,1,1]

也就是说,只要是arr1中出现过的值,我就在arr2中找到对应的下标,并且赋值为1。

 var now1 = new Date();

 //中间数组,用来记录arr出现过的值
var tmp = [];
arr.forEach(function(value){
tmp[value] = 1;
}); var arr2 = [];
tmp.filter(function(value,index){
if(value === 1){
arr2.push(index);
}
}) var now2 = new Date();
console.log(now2-now1);

执行结果: 5毫秒左右,10毫秒以内

那么这个结果就很明显,效率之间的差距不是一般的大。。。

从代码也可以看出来,都只有一层循环,那么效率就是O(n)了。还是非常快的。

二:使用sort进行排序。并使用filter过滤数组即可

 var now1 = new Date();
//使用sort从小到大排序并过滤掉前后相同的元素
var arr3 = arr.sort(function(a,b){
return a-b;
}).filter(function(value,index){
return arr[index] !== arr[index-1];
});
var now2 = new Date();
console.log(now2-now1);

执行结果: 80毫秒左右,100毫秒以内

可以看出来这个结果也还算可以(对比第一种)

不难看出来,node中sort的效率应该在nlogn(-。-废话么这不是)

并且2次遍历数组的消耗基本可以忽略不计(从第二种可以看出来)

那么为什么既然比第二个的效率低,又没第一个简洁,为什么要用这种方法呢,第一是可以扩宽思路,第二是因为这种方法的适用范围比第二种要高。

比如说,我要去重的数组不是数字,而是字符。那么第二种方法就无法使用(可以使用对象来实现,不过不知道对象遍历的效率高不高,留作以后测试)。并且, 第一种方法如果其中有一个数字为10E,那么空间消耗就很大(js中不确定,大部分语言是这样)。因为要开一个10E的数组。。。以前比赛的时候用c开个1000万的数组就超了。

三个方法放一次看下:

 var arr = [];
//生成随机数组
for (var i = 100000; i >= 0; i--) {
arr.push(Math.ceil(Math.random()*10000));
} //开始时间
var now1 = new Date();
var arr1 = arr.filter(function(value,index,arr){
return arr.indexOf(value,index+1) === -1
}) //结束时间
var now2 = new Date();
console.log(now2-now1); console.log("------------------------"); now1 = new Date(); //中间数组,用来记录arr出现过的值
var tmp = [];
arr.forEach(function(value){
tmp[value] = 1;
}); var arr2 = [];
tmp.filter(function(value,index){
if(value === 1){
arr2.push(index);
}
}) now2 = new Date();
console.log(now2-now1); console.log("------------------------"); now1 = new Date();
//使用sort从小到大排序并过滤掉前后相同的元素
arr3 = arr.sort(function(a,b){
return a-b;
}).filter(function(value,index){
return arr[index] !== arr[index-1];
});
//过滤数组
now2 = new Date();
console.log(now2-now1);

效率对比就非常明显了。

下面来说一下之前留下的坑:

1、为什么要用跨度呢,因为实验中发现(一开始并没有细想),如果数组跨度越下,那么第一种方法的速度就越快,但是其他两种,尤其是第二种的变化幅度就没那么剧烈,这是为什么呢?

其实是因为,indexOf的机制造成的。indexOf的实现是从你规定的位置开始往后找,找到第一个就停下来。所以很明显的,如果跨度越小,那么出现重复数字的几率就越高,那么就越有可能很快有返回结果,所以跨度越小就会越接近n的效率。

反观其他两个方法,不管你跨度多少,影响的都是数组的长度,那么影响就只在遍历一遍数组的效率这方面,所以就很小。

2、其实第一种方法的2000毫秒左右的效率是基于是随机数组的情况下,那么如果我们把数组改成顺序数组,也就是没有重复的数组呢?

我们来试验一下

var arr = [];
//生成顺序数组
for (var i = 100000; i >= 0; i--) {
arr.push(i);
}

看到了吧,这才是真正的n²的效率,之前的2000毫秒和这个还是有常数级别的区别的。

我们还发现,第三种方法的速度也变久了。将近翻了一倍,这个的话应该是sort内部实现的问题,我也不知道node内部sort使用的是什么排序,不过排序方法一般都是对顺序或者倒序和随机之间有一定的差距,这个很正常。

实验做完了,我们来总结一下:

第一种:

优点:简洁,明了,一定程度上可以看出对js的掌握程度,适用范围很广,并且不会改变数组元素的相对位置(分为对后去重和对前去重两种)。

缺点:效率非常慢,尤其是在重复数非常少的情况下。

第二种:

优点:速度非常快,受其他因素影响小(如果数量少,跨度大的话,也就是稀疏数组,反而会比其他的慢)。

缺点:适用范围比较小,只能适合数字数组,并且数组不能过大。如果为其他数组,可以使用对象作为数组。遍历使用for in 即可,效率和第三种差距不大。代码如下,可自行实验。

 var now1 = new Date();

 //中间对象
var tmp = {};
arr.forEach(function(value){
tmp[value+'we'] = 1;
}) var arr3 = [];
for(index in tmp){
if(tmp[index] === 1){
arr3.push(index);
}
} var now2 = new Date();
console.log(now2-now1);

第三种:

优点:速度相对较快,受其他因素影响不大,适用范围也相对较广。

缺点:中轨终于,适用范围不如第一种大,速度不如第三种快。会受其他因素一定的影响。

js面试题之数组去重对比的更多相关文章

  1. js经典试题之数组与函数

    js经典试题之数组与函数 1:列举js的全局函数? 答案:JavaScript 中包含以下 7 个全局函数escape( ).eval( ).isFinite( ).isNaN( ).parseFlo ...

  2. js 查找树节点 数组去重

    //查找树节点function findData(curOrg, id) { var array = []; if ((typeof curOrg == 'object') && (c ...

  3. js:多种方法实现数组去重

    面试的时候数组去重要多种方法实现, 只想到一种判断重复删除的方法,而且还没写对.后来大概看了一下网上的方法. 下午想到一个网上没见过的filter方法,于是整理了一下,基于以前看到的思想,然后用了一些 ...

  4. 58同城笔试题:数组去重;分饼干(分糖果);最小路径和(leetcode64)

    1. 数组去重 题目描述 /** * 有序数组去重 * 输出最终的数字个数 * 输入:1,2,2 * 输出:2 * @author Turing * */ 代码 import java.util.*; ...

  5. 亲测有效JS中9种数组去重方法

    码文不易,转载请带上本文链接,感谢~ https://www.cnblogs.com/echoyya/p/14555831.html 目录 码文不易,转载请带上本文链接,感谢~ https://www ...

  6. JS 使用 splice() 对数组去重

    一 问题 有如下 js 数组 connect_clients,需要在去掉所有元素中 user_id, goods_id 这两者的值都相同的元素. [ { id: 'eff040fb-92bc-4f24 ...

  7. JS合并多个数组去重算法

    var arr1 = ['a','b']; var arr2 = ['a','c','d']; var arr3 = [1,'d',undefined,true,null]; //合并两个数组,去重 ...

  8. js小技巧:数组去重

    JavaScript 数组中去除重复的数据 var arr = [1, 2, 2, 3, '1', null, 'a', 'b', 'a']; var t = {}; var result = arr ...

  9. js array.filter实例(数组去重)

    语法: 循环对数组中的元素调用callback函数, 如果返回true 保留,如果返回false 过滤掉,  返回新数组,老数组不变 var new_array = source_array.filt ...

随机推荐

  1. java并发编程(十九)障碍器CyclicBarrier

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17512983 CyclicBarrier(又叫障碍器)同样是Java 5中加入的新特性,使 ...

  2. 启动项目的时候报驱动错误: not support oracle driver 1.0

    问题:今天在使用pom导入oracle14的包时候发现怎么都下载不过来.网上查了一下发现是因为Oracle驱动需要官方授权,所以在pom.xml文件直接配置,无法下载成功. 解决方法就是下载oracl ...

  3. 2016huasacm暑假集训训练五 H - Coins

    题目链接:http://acm.hust.edu.cn/vjudge/contest/126708#problem/H 题意:A有一大堆的硬币,他觉得太重了,想花掉硬币去坐的士:的士司机可以不找零,但 ...

  4. IE7中使用Jquery动态操作name问题

    问题:IE7中无法使用Jquery动态操作页面元素的name属性. 在项目中有出现问题,某些客户的机器偶尔会有,后台取不到前台的数据值. 然开发和测试环境总是不能重现问题.坑爹之处就在于此,不能重现就 ...

  5. Android学习第三天-签名常用命令

    由于怕篇幅过长,所以把这个打包常用命令分开成两篇博文来进行讲解,下面我们直接进入主题吧. 8.keytool 这是我们JDK自带的密钥和证书管理工具 命令: -certreq 生成证书请求 -chan ...

  6. Windows Azure Storage (23) 计算Azure VHD实际使用容量

    <Windows Azure Platform 系列文章目录> 对于A系列和D系列的虚拟机来说,使用的是普通存储. 普通存储的存储资源,是按照每GB每月计费的.Microsoft Azur ...

  7. Cocos2d-x 3.x游戏开发之旅

    Cocos2d-x 3.x游戏开发之旅 钟迪龙 著   ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x ...

  8. CI Weekly #4 | 不同规模的团队,如何做好持续集成?

    CI Weekly 围绕『 软件工程效率提升』 进行一系列技术内容分享,包括国内外持续集成.持续交付,持续部署.自动化测试. DevOps 等实践教程.工具与资源,以及一些工程师文化相关的程序员 Ti ...

  9. iOS-多线程基础

    进程与线程: 1>   一个应用程序对应一个进程,一个进程帮助程序占据一块存储空间 2>   要想在进程中执行任务,就必须开启线程,一条线程就代表一个任务 3>   一个进程中允许开 ...

  10. 自定义模拟一个Spring IOC容器

    一.模拟一个IOC容器: 介绍:现在,我们准备使用一个java project来模拟一个spring的IOC容器创建对象的方法,也就是不使用spring的jar自动帮助我们创建对象,而是通过自己手动书 ...