js中方法的理解

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

前几日在做js-assessment时,发现其数组这一章里也有数组去重的这一问题:这个问题说起来十分简单,
就是把数组中重复的元素去除。其实个人感觉数组去重问题实际上就是排序的升级版,目前开来最好的去
重方法就是字典去重,这一点和排序中的基数排序不谋而合。下面就简单的说一说自己解决这个问题的思路。

编写AOP时间函数

对于解决数组去重算法的好坏,最终效率是第一位的,所以需要编写一个计算函数运行时间的切面函数。实
现如下:

Function.prototype.time = function() {
var t1 = +new Date()
, foo = this()
, t2 = +new Date()
return t2 - t1 //返回单位为毫秒
}
但是写完这个方法之后发现,对于要测试运行的函数而言,在进行测试之前不能够运行(即只能写成 foo.tim
e() 的样子),这样就不能用普通传参的方法对其进行参数传递。突然想到了在前几日看到过prototypejs中
的源码中有一个 bind 函数,其功能就在与给一个函数绑定特定上下文,且返回函数本身而不立即执行,于
是就马上实现了这样一个函数,代码如下:

Function.prototype.bind = function(ob) {
var fn = this
, slice = Array.prototype.slice
, args = slice.call(arguments, 1)
return function(){
return fn.apply(ob, args.concat(slice.apply(arguments)))
}
}
写完这两个,我们就可以对测试函数进行运行时间计算,假如数组为 arr ,测试函数为 delrep ,则在实际操
作中可以这样实现:delrep.bind(arr).time() (执行函数的同时输出运算时间)。

双重循环去重

在就去重方法讨论的文章中,愚人码头的文章里说到过这个方法,当然,作者本身也承认,这种双重for循环
嵌套的方法在大数据量的情况下十分耗时。作者的源代码引用如下:

Array.prototype.delRepeat=function(){
var newArray=new Array();
var len=this.length;
for (var i=0;ifor(var j=i+1;jif(this[i]===this[j]){
j=++i;
}
}
newArray.push(this[i]);
}
return newArray;
}
这里我也用ECMAScript中声明的 forEach 方法和 indexOf 方法模拟实现一下双重循环:

function delrep1() {
var n = []

this.forEach(function(v) {
if (n.indexOf(v) == -1)
n.push(v)
})
return n
}
作者的代码看起来像极了冒泡排序:每一次操作都会冒出一个没有重复元素的,放入新的数组(对应冒泡排序
冒出最小的)。而冒泡排序的时间复杂度是O(n^2),可以想见这个算法的效率着实不高。而在我的算法中采
用了 indexOf 的方法,没想这个遍历的效率要高很多,最终执行的时间要比作者的方法高不少。这里

贴一下
最终运行的时间(用随机生成的15w长度的数组进行测试,编译器使用的是想向大家极力推荐的nodejs,虽
然这里只用了它一个小小的功能):

malcolm@malcolm:~/test/aop$ node aop.js
method0: 1389ms #我的方法
method1: 9087ms #作者的方法
各中原理,还需要仔细的分析才行。

字典去重

之后作者提了一个字典去重的方法,我用自己的方法简化了一下:

function delrep2() {
var n = {}
, r = []

this.forEach(function(v){
if (!n[v]) {
n[v] = true
r.push(v)
}
})
return r
},
这个一看就很脸熟——传说中时间复杂度只有O(n)的基数排序么!类似与扑克牌的发牌,一次遍历什么的不
是最快捷了么。当然这里用到了“空间换时间”的策略,多出来一个庞大的字典,但是为了效率,做一点牺
也是必要的。运行的时间也令人叹为观止:

malcolm@malcolm:~/test/aop$ node aop.js
method0: 1389ms
method1: 9087ms
method2: 9ms
但是令人遗憾的是,这个方法是有bug的:你把所有的元素都转化成字典的键值key,也就是字符串,那必
然会出现1和'1'的问题。在数组中他们并不是重复元素,而这里只能保留一个。这可怎么办呢?在愚人码头
帖子的回复中,马上有人提到了,既然键值转化为字符串后失去了类型,那如果在转化之前给他加上类型会
怎么样呢?代码如下:

function delrep3() {
var n = {}
, r = []

this.forEach(function(v){
if (!n[typeof(v) + v]) {
n[typeof(v) + v] = true
r.push(v)
}
})
return r
},
不过作者在回复里说,这个方法的效率和两重循环的差不多,难道真的是这样么?实际测试了一下:

malcolm@malcolm:~/test/aop$ node aop.js
method0: 1389ms
method1: 9087ms
method2: 9ms
method3: 56ms
明显已经好了不少啊~可是可以看出因为 typeof 的原因,效率比第二个方法低了不少,但是没有bug的
优势足以弥补这一缺憾。

排序遍历

问题说到这里好像已经解决了,但是实际上背后的算法问题我自己还是没有搞清楚,希望以后能把详细的原
因补上。这里也写一个我自己最初想到的方法:要去重先遍历,依靠javascript自身的sort函数先帮我们过
一关。据说这个函数用的是O(nlgn)的快速排序,显然是已经比冒泡要好不少了。排完序之后重复的数据都
叠在一起,排头向栈里压数据,遇到与栈顶相同的元素则不压。我的算法如下:

function delrep4() {
var n = []

this.sort()
n.push(this[0])
this.forEach(function(v) {
if (v !== n[0])
n.unshift(v)
})
return n
}
最终的时间还是比较可观的,不过还是没有方法2的改进版

本好,数据如下:

malcolm@malcolm:~/test/aop$ node aop.js
method0: 1389ms
method1: 9087ms
method2: 9ms
method3: 56ms
method4: 88ms
看起来还不错~

在完成测试的同时参考了这篇文章:JS数组去重问题,文中还提到了用排序之后用splice删除重复元素的方法
,不得不承认这样确实节省了一部分空间,但是js的splice方法,要涉及删除后的数组整列移动,道爷可是在
蝴蝶书中点名说本方法“大型数组中效率较低”的。自己在测试的时候发现在15w数据量的时候用splice还是
效率还是可以,再多的话就明显不如我自己的方法了。不过综合来说,如果可以保证数组内部是纯数值,用
字典排序绝对还是最明智的选择。

相关文档
最新文档