我有一个非常简单的JavaScript数组,可能包含也可能不包含重复项。

var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];

我需要删除重复项并将唯一值放入新数组。

我可以指出我尝试过的所有代码,但我认为它们没有用,因为它们不起作用。我也接受jQuery解决方案。

类似的问题:

获取数组中的所有非唯一值(即:重复/多次出现)


当前回答

TL;博士

使用Set构造函数和排列语法:

uniq = [...new Set(array)];

(注意var uniq将是一个数组…new Set()将其转换为一个集合,但[…]将其再次转换为数组)


“聪明”但幼稚的方式

uniqueArray = a.filter(function(item, pos) {
    return a.indexOf(item) == pos;
})

基本上,我们遍历数组,并检查每个元素在数组中的第一个位置是否等于当前位置。显然,对于重复的元素,这两个位置是不同的。

使用过滤器回调的第三个(“this array”)参数,我们可以避免数组变量的关闭:

uniqueArray = a.filter(function(item, pos, self) {
    return self.indexOf(item) == pos;
})

虽然简洁,但该算法对于大型阵列(二次时间)不是特别有效。

拯救的桥段

function uniq(a) {
    var seen = {};
    return a.filter(function(item) {
        return seen.hasOwnProperty(item) ? false : (seen[item] = true);
    });
}

通常是这样做的。其想法是将每个元素放置在哈希表中,然后立即检查其是否存在。这给了我们线性时间,但至少有两个缺点:

由于哈希键在JavaScript中只能是字符串或符号,因此该代码不区分数字和“数字字符串”。也就是说,uniq([1,“1”])将只返回[1]出于同样的原因,所有对象都将被视为相等:uniq(〔{foo:1},{foo:2}〕)将只返回〔{foo:1}〕。

也就是说,如果数组只包含基元,而不关心类型(例如,总是数字),那么这个解决方案是最佳的。

两个世界中最好的

通用解决方案结合了这两种方法:它使用哈希查找原语和线性搜索对象。

function uniq(a) {
    var prims = {"boolean":{}, "number":{}, "string":{}}, objs = [];

    return a.filter(function(item) {
        var type = typeof item;
        if(type in prims)
            return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
        else
            return objs.indexOf(item) >= 0 ? false : objs.push(item);
    });
}

排序| uniq

另一个选项是先对数组进行排序,然后删除与前一个元素相等的每个元素:

function uniq(a) {
    return a.sort().filter(function(item, pos, ary) {
        return !pos || item != ary[pos - 1];
    });
}

同样,这不适用于对象(因为排序时所有对象都是相等的)。此外,我们默默地改变了原始数组,这是一个副作用——不好!然而,如果您的输入已经排序,这就是方法(只需从上面删除排序)。

唯一依据。。。

有时需要根据一些标准而不仅仅是相等来取消列表的限定,例如,过滤出不同但共享某些属性的对象。这可以通过传递回调来实现。此“key”回调应用于每个元素,并删除具有相等“key”的元素。由于键需要返回一个原语,所以哈希表在这里可以正常工作:

function uniqBy(a, key) {
    var seen = {};
    return a.filter(function(item) {
        var k = key(item);
        return seen.hasOwnProperty(k) ? false : (seen[k] = true);
    })
}

一个特别有用的键()是JSON.stringify,它将删除物理上不同但“看起来”相同的对象:

a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]

如果密钥不是原始的,则必须使用线性搜索:

function uniqBy(a, key) {
    var index = [];
    return a.filter(function (item) {
        var k = key(item);
        return index.indexOf(k) >= 0 ? false : index.push(k);
    });
}

在ES6中,您可以使用集合:

function uniqBy(a, key) {
    let seen = new Set();
    return a.filter(item => {
        let k = key(item);
        return seen.has(k) ? false : seen.add(k);
    });
}

或地图:

function uniqBy(a, key) {
    return [
        ...new Map(
            a.map(x => [key(x), x])
        ).values()
    ]
}

这两个键也可以使用非基本键。

第一个还是最后一个?

通过键删除对象时,可能需要保留“相等”对象中的第一个或最后一个。

使用上面的Set变量保留第一个,使用Map保留最后一个:

函数uniqByKeepFirst(a,key){let seen=new Set();返回a.filter(项=>{设k=密钥(项);看见了吗?false:见。添加(k);});}函数uniqByKeepLast(a,key){返回[…新建地图(a.map(x=>[键(x),x])).values()]}//数据=[{a:1,u:1},{a:2,u:2},{a:3,u:3},{a:4,u:1},{a:5,u:2},{a:6,u:3},];console.log(uniqByKeepFirst(data,it=>it.u))console.log(uniqByKeepLast(data,it=>it.u))

图书馆

下划线和Lo Dash都提供uniq方法。他们的算法基本上类似于上面的第一段,归结起来就是:

var result = [];
a.forEach(function(item) {
     if(result.indexOf(item) < 0) {
         result.push(item);
     }
});

这是二次的,但还有一些很好的附加好处,比如包装原生indexOf,通过键进行uniqify(用他们的话说是迭代)的能力,以及对已排序数组的优化。

如果您正在使用jQuery,并且无法忍受前面没有一美元的任何东西,则情况如下:

  $.uniqArray = function(a) {
        return $.grep(a, function(item, pos) {
            return $.inArray(item, a) === pos;
        });
  }

这也是第一片段的变体。

表演

JavaScript中的函数调用非常昂贵,因此上述解决方案虽然简洁,但效率并不高。为了获得最大的性能,用循环替换过滤器,并消除其他函数调用:

function uniq_fast(a) {
    var seen = {};
    var out = [];
    var len = a.length;
    var j = 0;
    for(var i = 0; i < len; i++) {
         var item = a[i];
         if(seen[item] !== 1) {
               seen[item] = 1;
               out[j++] = item;
         }
    }
    return out;
}

这段丑陋的代码与上面的第3段代码相同,但速度快了一个数量级(截至2017年,速度只有两倍-JS核心人员做得很好!)

函数uniq(a){var seen={};return a.filter(函数(项){返回已查看。hasOwnProperty(项)?假:(见[项目]=真);});}函数uniq_fast(a){var seen={};var out=[];var len=a.length;变量j=0;对于(var i=0;i<len;i++){var项目=a[i];if(参见[项目]!==1){见[项目]=1;out[j++]=项目;}}返回;}/////变量r=[0,1,2,3,4,5,6,7,8,9],a=[],LEN=1000,回路=1000;而(LEN-)a=交流电(r);var d=新日期();对于(var i=0;i<回路;i++)uniq(a);document.write('<br>uniq,ms/loop:'+(new Date()-d)/LOOPS)var d=新日期();对于(var i=0;i<回路;i++)uniq_fast(a);document.write('<br>uniq_fast,ms/loop:'+(new Date()-d)/LOOPS)

ES6

ES6提供了Set对象,这使事情变得更加简单:

function uniq(a) {
   return Array.from(new Set(a));
}

or

let uniq = a => [...new Set(a)];

注意,与python不同,ES6集合是按插入顺序迭代的,因此这段代码保留了原始数组的顺序。

然而,如果您需要具有唯一元素的数组,为什么不从一开始就使用集合呢?

发电机

可以在相同的基础上构建uniq的“懒惰”、基于生成器的版本:

从参数中获取下一个值如果已经看到了,就跳过它否则,生成它并将其添加到已看到的值集合中

函数*uniqIter(a){let seen=new Set();for(设a的x){如果(!seed.has(x)){见添加(x);产量x;}}}//示例:函数*randomsBelow(极限){而(1)yield Math.floor(Math.random()*limit);}//注意randomsBelow是无穷无尽的计数=20;极限=30;for(uniqIter(randomsBelow(limit))的let r){控制台日志(r);如果(--count==0)打破}//读者练习:如果我们将“limit”设置为小于“count”会发生什么?为什么

其他回答

VanillaJS:使用像Set这样的Object删除重复项

您可以始终尝试将其放入对象中,然后遍历其关键点:

function remove_duplicates(arr) {
    var obj = {};
    var ret_arr = [];
    for (var i = 0; i < arr.length; i++) {
        obj[arr[i]] = true;
    }
    for (var key in obj) {
        ret_arr.push(key);
    }
    return ret_arr;
}

Vanilla JS:通过跟踪已经看到的值来删除重复项(订单安全)

或者,对于订单安全版本,使用一个对象来存储所有以前看到的值,并在添加到数组之前检查值。

function remove_duplicates_safe(arr) {
    var seen = {};
    var ret_arr = [];
    for (var i = 0; i < arr.length; i++) {
        if (!(arr[i] in seen)) {
            ret_arr.push(arr[i]);
            seen[arr[i]] = true;
        }
    }
    return ret_arr;

}

ECMAScript 6:使用新的Set数据结构(顺序安全)

ECMAScript 6添加了新的Set Data Structure,它允许您存储任何类型的值。Set.values按插入顺序返回元素。

function remove_duplicates_es6(arr) {
    let s = new Set(arr);
    let it = s.values();
    return Array.from(it);
}

示例用法:

a = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];

b = remove_duplicates(a);
// b:
// ["Adam", "Carl", "Jenny", "Matt", "Mike", "Nancy"]

c = remove_duplicates_safe(a);
// c:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]

d = remove_duplicates_es6(a);
// d:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]

顶级答案的复杂度为O(n²),但这可以通过使用对象作为哈希来实现,只需O(n):

function getDistinctArray(arr) {
    var dups = {};
    return arr.filter(function(el) {
        var hash = el.valueOf();
        var isDup = dups[hash];
        dups[hash] = true;
        return !isDup;
    });
}

这将适用于字符串、数字和日期。如果您的数组包含对象,则上述解决方案将不起作用,因为当强制为字符串时,它们都将具有“[objectObject]”(或类似的值),这不适合作为查找值。通过在对象本身上设置标志,可以获得对象的O(n)实现:

function getDistinctObjArray(arr) {
    var distinctArr = arr.filter(function(el) {
        var isDup = el.inArray;
        el.inArray = true;
        return !isDup;
    });
    distinctArr.forEach(function(el) {
        delete el.inArray;
    });
    return distinctArr;
}

2019年编辑:JavaScript的现代版本使这个问题更容易解决。无论数组中是否包含对象、字符串、数字或任何其他类型,都可以使用Set。

function getDistinctArray(arr) {
    return [...new Set(arr)];
}

实现如此简单,不再需要定义函数。

虽然ES6解决方案是最好的,但我很困惑,没有人展示了以下解决方案:

function removeDuplicates(arr){
    o={}
    arr.forEach(function(e){
        o[e]=true
    })
    return Object.keys(o)
}

这里需要记住的是,对象必须具有唯一的键。我们正在利用这一点来删除所有重复项。我本以为这是最快的解决方案(在ES6之前)。

请记住,这也会对数组进行排序。

删除字符串重复项的最简单方法是使用关联数组,然后遍历关联数组以使列表/数组返回。

如下所示:

var toHash = [];
var toList = [];

// add from ur data list to hash
$(data.pointsToList).each(function(index, Element) {
    toHash[Element.nameTo]= Element.nameTo;
});

// now convert hash to array
// don't forget the "hasownproperty" else u will get random results 
for (var key in toHash)  {
    if (toHash.hasOwnProperty(key)) { 
      toList.push(toHash[key]);
   }
}

瞧,现在复制品不见了!

如果您自己创建数组,可以在插入数据时进行检查,从而为自己保存一个循环和额外的唯一过滤器;

var values = [];
$.each(collection, function() {
    var x = $(this).value;
    if (!$.inArray(x, values)) {
        values.push(x);
    }
});