首先让我们看下 YUI 是如何处理的:

var toObject = function(a) {
    var o = {};
    for (var i = 0; i < a.length; i = i+1) {
     o[a[i]] = true;
    }
    return o;
};

var keys = function(o) {
    var a=[], i;
    for (i in o) {
        if (lang.hasOwnProperty(o, i)) { // YUI的方法
            a.push(i);
        }
    }
    return a;
};

var uniq = function(a) {
    return keys(toObject(a));
};

详细分析,见同事 长天 的分享 《巧妙去除数组中的重复项》

自己使用的方式与 YUI 的方式十分相似,不过仅使用了一次循环便完成了删除数组中重复项,如下:

var uniq = function (arr) {
    var a = [],
        o = {},
        i,
        v,
        len = arr.length;

    if (len < 2) {
        return arr;
    }

    for (i = 0; i < len; i++) {
        v = arr[i];
        if (o[v] !== 1) {
            a.push(v);
            o[v] = 1;
        }
    }

    return a;
}

经过了简单的测试:自己使用的方式性能远远高于 YUI 的方式。

抛砖引玉,欢迎大家提供更好的处理方法。

2009年12月28日更新:

以上两种函数方法暂时都不能处理复杂的含有混合类型的数组(感谢 小猫 提出的疑问),如:[0,"0",1,"1",0]、["null",null]。

对于能够约定类型为数字(注:要求非0开头的数字,小数除外)或字符串的数组,我们可以用改进后的函数方法(感谢 closurecache 提供的思路):

var uniq = function (arr) {
    var a = [],
        o = {},
        i,
        v,
        cv, // corrected value
        len = arr.length;

    if (len < 2) {
        return arr;
    }

    for (i = 0; i < len; i++) {
        v = arr[i];

        /* closurecache 提供的函数中使用的是  cv = v + 0;,
         * 这样就无法辨别类似[1, 10, "1", "10"]的数组,
         * 因为运算后 => 1, 10, 10, 100,很明显,出现了重复的标示符。
         * 加前面就难道没问题吗?
         * 有的:数组中不能出现类似01 、001,以 0 开头的数字,
         * 但适用性比原先更广。
         */
        cv = 0 + v;

        if (!o[cv]) {
            a.push(v);
            o[cv] = true;
        }
    }

    return a;
}

如果大家想在此解题思路的基础上,更完美一点,推荐 Dexter.Yy 的方法,进行类型判断,给予唯一标示符,详见 评论 20 楼

没有最好,只有最合适的方式,其实使用 Array.indexOf() 的思路也是不错的选择,对于已经支持的浏览器直接用原生的 Array.indexOf() 方法,对于未支持的,我们增加 Array.indexOf() 方法,如下:

if(!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (obj, fromIndex) {
        if (fromIndex == null) {
            fromIndex = 0;
        } else if (fromIndex < 0) {
            fromIndex = Math.max(0, this.length + fromIndex);
        }

        for (var i = fromIndex; i < this.length; i++) {
            if (this[i] === obj)
                return i;
            }
        return -1;
    };
}

接下来,实现的过程就非常简单了。

对于使用 Array.indexOf() 方法实现方案的优化提示:找到相同值时,从数组中移除,以减少下次遍历的量。



共有36 条评论

  1. 1. 头像 千吨泥

    妙! 可以说将 缓存数据 发挥到了极致……

  2. 2. 头像 小猫

    好方法!
    不过突然闪现一个感觉很钻牛角尖的问题想请教:
    没考虑数字和数字字符串混合出现的情况,比如 [0,"0",1,"1",0]。
    是这种数组太不常见不予考虑,还是程序出现这样一个数组就很有问题?

  3. 3. 头像 黑眼刀豆

    很好的算法

  4. 4. 头像 怿飞

    @小猫 非常感谢你提出的问题:“没考虑数字和数字字符串混合出现的情况”,的确对于[0,"0",1,"1",0],函数方法没办法处理正确。

  5. 5. 头像 长天

    哥, 还是你牛掰, 看到好的方法, 还能继续思考如何改进得更好, 你是怎么做到的?

  6. 6. 头像 popomore

    o[v] = 1;
    验证的时候为什么要设为1呢,如果只是标示符的话布尔型更好吧。
    if (!o[v]) {
    a.push(v);
    o[v] = true;
    }

  7. 7. 头像 行骏

    我的确也很迷惑o = {‘name’:'movinghorse’}和o = {name:’movinghorse’}有什么区别?

  8. 8. 头像 popomore

    关于上面数字字符的混排的一些想法
    添加了一个标示,两位二进制,第一位为数字,第二位为字符,存在为1,不存在为0
    先检测是否存在,如果存在再去检测是否有数字字符的情况,当被设为3(二进制为11)的时候,表明是数字和数字字符都已经存在

    if ( !o[v] || o[v] !== 3) {
    a.push(v);

    if(!o[v]){
    o[v] = 0;
    }

    if(typeof v === 'number'){
    o[v] = o[v] + 2;
    } else {
    o[v] = o[v] + 1;
    }

    }

  9. 9. 头像 closurecache

    Array.prototype.unique = function(){
    var tempObj = {}, res = [];
    for (var i = 0,len=this.length; i < len; i++) {
    var temp = this[i], tempForJudge = temp+0;
    if (!tempObj[tempForJudge]) {
    tempObj[tempForJudge] = 1;
    res.push(temp);
    }
    }
    return res;
    };
    //解决数字和字符串混合的情况

  10. 10. 头像 army8735

    之前我也设想过这样用hash存储数组项是否出现过的方法,也没想到数字和字符串一起出现时该怎么办。

    不过mootools里提供了比较好的强类型判断,借助它的话可以办到。这样每个hash存储时不能只存bool或者1了,要存其类型(string、number)。判断是否有的时候判断!== undefined即可。

  11. 11. 头像 army8735

    说错了,比较的时候是比较值(类型),不是未定义。

  12. 12. 头像 closurecache

    您的评论正在穿越伟大的GFW……
    这是啥呀??

  13. 13. 头像 closurecache

    Array.prototype.unique = function(){
    var tempObj = {}, res = [];
    for (var i = 0,len=this.length; i < len; i++) {
    var temp = this[i], tempForJudge = temp+0;
    if (!tempObj[tempForJudge]) {
    tempObj[tempForJudge] = 1;
    res.push(temp);
    }
    }
    return res;
    };
    //解决数字和字符串混合的情况
    //发帖得翻墙,唉。。。

  14. 14. 头像 army8735

    http://army8735.org/2009/12/27/523.html
    我也分享出来了。

  15. 15. 头像 天堂左我往右

    用两个object,一个放数字,一个放字符串,嘿嘿

    数组不能是对象噢…

  16. 16. 头像 怿飞

    @行骏 没什么区别的,主要不在于这个的改变,而在性能上的提升,呵呵

  17. 17. 头像 怿飞

    @army8735 你的方式不错,非常感谢,明天再修正一下。
    @opomore 其实不仅仅是数字和数字字符有问题,比如类似这样的数组也同样有问题的:[null,'null']。
    @closurecache 最后提供的方法应该可以解决这样的问题,不过可能要验证下是不是所有的情况都可以。

    还是,非常感谢大家的讨论和建议!

  18. 18. 巧妙去除数组中的重复项(续) - TCer's Blog

    [...] 圆心的文章中, 对去除数组重复项的方法作了进一步的改进, 使得性能再次提升. 但如它文章中所提到的: 上述两种函数方法暂都无法处理好数字和数字字符串混合出现的情况,比如 [0,"0",1,"1",0]。, 这里, 我分享一下我的解决办法… [...]

  19. 19. 头像 welefen

    通过对象的实现方式一种是给对象加个标记,如jquery实现方式,这种方式无法解决一些直接量,如111,’abc’,null等。
    另一种是执行tostring方法,这种方式无法解决多个对象的tostring结果相同,如:new String(‘abc’)和new String(‘abc’)。
    其实有一种更好的实现方式,并且实现效率上要高很多,即利用数组的lastIndexOf和splice方法。
    http://www.welefen.com/javascript-array-unique/

  20. 20. 头像 Dexter.Yy

    啊,我是被army引来的~~

    我在土豆网写一个实现,支持混合类型:

    TUI.unique = function( array ) {
    var ret = [], record = {}, it, tmp;
    var type = {
    “number”: function(n){ return “_TUI_num” + n; },
    “string”: function(n){ return n; },
    “boolean”: function(n){ return “_TUI_boolean” + n; },
    “object”: function(n){ return n === null ? “TUI_null” : $.data(n); },
    “undefined”: function(n){ return “_TUI_undefined”; }
    };
    for ( var i = 0, length = array.length; i < length; i++ ) {
    it = tmp = array[i];
    tmp = type[typeof it](it);
    if( !record[tmp] ) {
    ret.push(it);
    record[tmp] = true;
    }
    }
    return ret;
    };

    测试:

    var b=[1,3,5];
    TUI.unique([1,3,4,5,null,false,$(".pack")[0],b,"ab","cc",[1,3],3,6,b,1,false,null,"null","","false","",$(".pack")[0],"cc"]);

  21. 21. 头像 怿飞

    @welefen 提供的写法是比较标准的写法,且对于FF3.5+等实现了Array.indexOf()方法的浏览器来说,效果会更效率,期待未来所有浏览器都支持indexOf方法。

    @Dexter.Yy 谢谢你的思路,很不错,昨天晚上也有想到过,非常感谢。

  22. 22. 头像 Dexter.Yy

    喔对了,忘了一件事情,我那个方法里面有用到jQuery.data,自己实现一个也很简短,就是给每个对象(包括DOM,函数,数组)生成一个唯一的流水号,绑在它的属性上,当然有些人可能不喜欢这样,不过绝大多数应用场合里是没影响的,换来的是简洁和效率

  23. 23. 头像 army8735

    前面+0这个方式很巧妙,我其实也想到了后面+类型这种作为key的方法。

  24. 24. 头像 cc

    20楼的不错哦
    解决了object的问题

  25. 25. 头像 cc

    根据20楼的改一下
    function unique(array) {
    var ret = [], record = {},it,tmp,obj = “__object__”, bak = [],i,len;
    var type ={
    “number”: function(n) { return “__number__” + n; },
    “string”: function(n) { return “__string__” + n; },
    “boolean”: function(n) { return “__boolean__” + n; },
    “undefined”: function(n) { return “__undefined__”; },
    “object”: function(n) {
    return n === null ? “__null__” : obj in n ? n[obj] : ( n[obj] = obj + bak.push(n) );
    }
    };
    for (i = 0, len = array.length; i < len; i++) {
    it = array[i]; tmp = type[typeof it](it);
    if (!(tmp in record)) { ret.push(it); record[tmp] = true; }
    }
    for (i = 0, len = bak.length; i < len; delete bak[i++][obj]) { }
    return ret;
    };

  26. 26. 头像 kangre

    说不错,挺完善的!

  27. 27. 头像 netwjx

    重复push应该没有直接赋值快吧,可以使a初始为长度和arr长度相同,最后再slice,中间需要计算a的实际长度

  28. 28. 头像 天堂左我往右

    加0真是个好方法…

  29. 29. 头像 Robin

    自己尝试写了一个,最坏的时间复杂度为O[0.5*n(n-1)]
    支持任意混合类型
    [code]
    if (Array.prototype.unique) {
    Array.prototype.unique = function(){
    if (this.length a.length) {//all done
    break;
    }
    }
    return a;
    }
    }
    [/code]

  30. 30. 删除数组的重复项 ­ army8735

    [...] 怿飞在同名文章《删除数组中重复项(uniq)》中分享了他的方法,时间复杂度和空间复杂度均为O(n),但还无法解决弱类型的问题。凑巧我也思考过类似的方法,在此基础上修改一下,可以KO弱类型问题。 [...]

  31. 31. 凌乱的JS例子整理 – 足跡偏右

    [...] 3.快速去除数组重复项。来自怿飞的《删除数组中重复项(uniq)》 。 [...]

  32. 32. 删除数组中重复项(uniq) | 前端开发吧

    [...] 如果大家想在此解题思路的基础上,更完美一点,推荐 Dexter.Yy 的方法,进行类型判断,给予唯一标示符,详见 评论 20 楼。 [...]

  33. 33. 删除数组中重复项(uniq) - 隐遁峰

    [...] 如果大家想在此解题思路的基础上,更完美一点,推荐 Dexter.Yy 的方法,进行类型判断,给予唯一标示符,详见 评论 20 楼。 [...]

  34. 34. 头像 笨蛋的座右铭

    我也来给一个:
    function unique(arr){
    arr.sort();
    var myArr = [];
    for(var i=0,j=arr.length,k=0;i<j;i++){
    if(myArr[k-1] != arr[i]){
    myArr[k++] = arr[i];
    }
    }
    return myArr;
    }
    缺点:sort行为提供了一些默认行为对数组中包含undefined,null的进行特殊处理,行为不能控制

  35. 35. 头像 笨蛋的座右铭

    再补充一下优点:同样,也正是因为sort的默认行为,解决了类似于[1,'10'10]的重复问题

  36. 36. js数组去重复的问题 | javascript

    [...] http://www.planabc.net/2009/12/26/array_uniq/ 此条目发表在 js 分类目录。将固定链接加入收藏夹。 [...]

发表评论

(必填)

(必填,会为您保密)

评论仅支持“a、abbr、strong、em、blockquote、code”几个简单的标签