realazy


JavaScript 数组的 uniq 方法

来自某个nb招聘的题目:

请给Array本地对象增加一个原型方法,它的用途是删除数组条目中重复的条目(可能有多个),返回值是一个包含被删除的重复条目的新数组。这是我的答案:

新解

Array.prototype.uniq = function(){
    var resultArr = [],
        returnArr = [],
        i = 1,
        origLen = this.length,
        resultLen;
    function include(arr, value){
        for (var i=0, n=arr.length; i<n; ++i){
            if (arr[i] === value){
                return true;
            }
        }
        return false;
    }
    resultArr.push(this[0]);
    for (i; i<origLen; ++i){
        if (include(resultArr, this[i])){
            returnArr.push(this[i]);
        } else {
            resultArr.push(this[i]);
        }
    }
    resultLen = resultArr.length;
    this.length = resultLen;
    for (i=0; i<resultLen; ++i){
        this[i] = resultArr[i];
    }
    return returnArr;
}

这种解法在整个过程对原有数组的改变只有两次,效率比其他两种高了2个数量级左右!可在此测试三种解法的性能。

旧解

以下至“关于测试案例”之间皆为旧文,若阅读不顺,忽略之。

Array.prototype.uniq_slow = function(){
    var ret = [],
        i = 0,
        j = 0;
    while (undefined !== this[i]){
        j = i + 1;
        while(undefined !== this[j]){
            if (this[i] === this[j]){
                ret.push(this.splice(j, 1)[0]);
            } else {
                ++j;
            }
        }
        ++i;
    }
    return ret;
}

感谢猫仔提示,这道题目很容易让人产生误读。看清了题目后更新了。

为何用 while 而不是 for? 因为这个数组总是在变化,每次循环都得重新计算 length. 按理说,使用 while 效率会更高,尤其数组很大的时候。

欢迎大家交流讨论。

感谢 fdcn 提示,更新之。这里确实是容易犯错。

猜想由于强类型判断导致性能不高(可在此测试),因此此种做法未见有性能的提升(还稍微慢了一些),而且还不能传递类似 [1,,,2,,] 这样的数组。所以还是淘宝UED上的解法比较科学(当然不是没有改进之处,比如不应该在 for 循环中声明变量)。

其实,这篇blog的意义在探讨如何避免无意义的消耗(比如计算 length)。但是鱼和熊掌不能兼得是自古之理,顾此失彼。当然,办法不是没有,比如数组的 forEach, map 方法等,可惜只有 gecko 浏览器才支持。

关于测试案例

数组是随机产生的1-100之间的整数,长度为5000,每个相同的大约重复5次。三个测试数组的元素构成是一致的。

总结

对数组的改变开销巨大,如果可能,尽量在不改变原有数组的情况下进行操作,如最终需要改变数组自身,可将结果赋予原有数组来操作。另外,对于 length 的计算,似乎效率并未受其影响。

啥时候我也该进补算法了,唉。软肋啊。

推荐阅读: 王元涛同学的 JavaScript 数组的 uniq 方法

22 Responses to “JavaScript 数组的 uniq 方法”

  1. 聪明猫游击队 Says:

    请教一下,var ret = this.slice(); 的作用是什么,仅仅是返回调用uniq()方法的那个数组么?

  2. fdcn Says:

    把[1,2,3,3,null, null,2]代入你的算法中試試

  3. sv Says:

    我测试了,尽管你方法没有重复计算length,但是效率还比淘宝UED那个差一点点,单纯的length对效率的影响不是太大

  4. 手气不错 Says:

    正如 fdcn 兄弟所言,确实有这样的问题,我也写了我的一个实现算法:

    http://www.gracecode.com/Archive/Display/319

    欢迎大家继续交流

  5. Kindy Says:

    好像还是有问题,呵呵。

    while (’undefined’ !== typeof this[i])

    while(’undefined’ !== this[j])
    不一样

    是否应该为
    while(undefined !== this[j])
    即可?

  6. 手气不错 Says:

    不好意思,再次更新。根据上述的问题,我重新优化了下算法,并将 Lazy 兄弟的“思想”加了进来,于是就有如下的代码了:

    Array.prototype.uniq = function(){
    var i = 0, j = 0;
    while (undefined !== this[i]){
    j = i + 1;
    while(undefined !== this[j]){
    if (this[i] === this[j]) {
    this.splice(j, 1);
    }
    ++j;
    }

    ++i;
    }

    return this;
    }

    我写的相对几个函数的链接: http://www.gracecode.com/Archive/Display/319

  7. 王元涛 Says:

    我在你的算法框架下改进成一个O(N)的算法,见http://www.pkblogs.com/todwang/2007/12/javascript-uniq.html

  8. Kindy Says:

    如果数组里面元素类型是一样的话,换成下面这样会更快(时间缩短一半多):
    Array.prototype.uniq2 = function(){
    var resultArr = [],
    returnArr = [],
    tempHash = {},
    i = 1,
    v,
    origLen = this.length,
    resultLen;
    resultArr.push(this[0]);
    for (i; i

  9. Kindy Says:

    for (; i

  10. Delta Says:

    理论上unique_slow那个会快……
    个人认为是splice()比较耗性能 所以实际会慢很多

  11. Kejun Says:

    不用push会快一些

  12. fdcn Says:

    测试了一下,你的新算法中function include函数单独做成Array.prototype.include,能更快一点。

  13. boin Says:

    跟PrototypeJs的实现类似了。

    如fdcn所说,include作为闭包函数,效率是不如拿出来做一个全局的函数。

  14. wondger Says:

    这两个函数写的真好~···
    function include(arr, value){
    for (var i=0, n=arr.length; i

  15. happierbee Says:

    其实没有必要用新的数组,可以在原数组基础上修改:
    Array.prototype.uniq2 = function () {
    function seen( arr, ele, len ) {
    for ( var i = 0; i

  16. bob_jia Says:

    我也写了下,貌似性能还不错。
    http://www.zhaozhaozhu.com/bob/bob_array_uniq.html

    另外,前两种方法的输出结果好象不太对吧。

  17. tansia Says:

    又学了一招。

  18. freeman983 Says:

    不懂算法,利用hash实现了一个

    Array.prototype.uniq_freeman983 = function(){
    window.status=’freeman983′;
    var oa = this;
    var ya = new Object();
    var ra = [];

    for(var i = 0; i

  19. freeman983 Says:

    不懂算法,利用hash实现了一个

    http://freeman983.javaeye.com/blog/163799

  20. 闲耘 Says:

    看了“博主”和“博客”的种种算法,很快,但是结果都不正确。

  21. 闲耘 Says:

    题目我好像理解错了,但是:
    1. 这个题目本身有歧义,条件也不明确(例如没有说明方法是否影响数组本身);
    2. 我认为返回“被删除元素”(所组成)的新数组没有什么意义;
    3. 返回删除(重复)元素后的新数组不需要这些算法。

  22. lone Says:

    freeman983的思路才叫巧妙…相形见绌阿~~赫赫

Leave a Reply


realazy (懒到死) is proudly powered by WordPress | Entries (RSS) and Comments (RSS)