从 JS 数组中删除重复的值

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

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

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

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

类似的问题:

答案

TL; DR

使用Set构造函数和spread 语法

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

“聪明” 但幼稚的方式

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

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

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

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);
    });
}

排序优衣库

另一种选择是先对数组排序,然后删除等于前一个元素的每个元素:

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

同样,这不适用于对象(因为所有对象的sort相等)。另外,我们无声地更改了原始数组作为副作用 - 不好!但是,如果您的输入已经排序,这就是方法(只需从上面删除sort )。

独一无二...

有时,我们希望根据某些条件(不仅仅是相等性)来对列表进行唯一化,例如,过滤出不同但共享某些属性的对象。可以通过传递回调来优雅地完成此操作。此 “键” 回调将应用于每个元素,并且删除具有相等 “键” 的元素。由于预计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是一个特别有用的key() ,它将删除物理上不同但 “看起来” 相同的对象:

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

如果key不是原始key则必须诉诸线性搜索:

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 中,您可以使用Set

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

Map

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

两者也都可以与非原始键一起使用。

首先还是最后?

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

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

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


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

//

data = [
    {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 ,按键进行唯一化的能力(用术语iteratee表示)以及对已排序数组的优化。

如果您使用的是 jQuery,但在没有美元之前不能忍受任何事情,它会像这样:

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

再次是第一个代码段的变体。

性能

函数调用在 JavaScript 中非常昂贵,因此上述解决方案尽管非常简洁,但并不是特别有效。为了获得最佳性能,请用循环替换filter并摆脱其他函数调用:

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 核心人员做得很好!)

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

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;
}

/////

var r = [0,1,2,3,4,5,6,7,8,9],
    a = [],
    LEN = 1000,
    LOOPS = 1000;

while(LEN--)
    a = a.concat(r);

var d = new Date();
for(var i = 0; i < LOOPS; i++)
    uniq(a);
document.write('<br>uniq, ms/loop: ' + (new Date() - d)/LOOPS)

var d = new Date();
for(var i = 0; i < LOOPS; 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));
}

要么

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

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

但是,如果需要具有唯一元素的数组,为什么不从一开始就使用集?

发电机

A“懒”,基于生成器的版本uniq可建在同一基础上:

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

function* uniqIter(a) {
    let seen = new Set();

    for (let x of a) {
        if (!seen.has(x)) {
            seen.add(x);
            yield x;
        }
    }
}

// example:

function* randomsBelow(limit) {
    while (1)
        yield Math.floor(Math.random() * limit);
}

// note that randomsBelow is endless

count = 20;
limit = 30;

for (let r of uniqIter(randomsBelow(limit))) {
    console.log(r);
    if (--count === 0)
        break
}

// exercise for the reader: what happens if we set `limit` less than `count` and why

快速而肮脏的使用 jQuery:

var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
    if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});

厌倦了使用 for 循环或 jQuery 查看所有不良示例。如今,JavaScript 具有完美的工具:排序,映射和归约。

在保持现有订单的同时减少 Uniq

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

var uniq = names.reduce(function(a,b){
    if (a.indexOf(b) < 0 ) a.push(b);
    return a;
  },[]);

console.log(uniq, names) // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]

// one liner
return names.reduce(function(a,b){if(a.indexOf(b)<0)a.push(b);return a;},[]);

排序更快的 uniq

可能有更快的方法,但是这一方法相当不错。

var uniq = names.slice() // slice makes copy of array before sorting it
  .sort(function(a,b){
    return a > b;
  })
  .reduce(function(a,b){
    if (a.slice(-1)[0] !== b) a.push(b); // slice(-1)[0] means last item in array without removing it (like .pop())
    return a;
  },[]); // this empty array becomes the starting value for a

// one liner
return names.slice().sort(function(a,b){return a > b}).reduce(function(a,b){if (a.slice(-1)[0] !== b) a.push(b);return a;},[]);

2015 年更新:ES6 版本:

在 ES6 中,您可以使用 Sets and Spread 来轻松且高效地删除所有重复项:

var uniq = [ ...new Set(names) ]; // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]

根据出现次数排序:

有人问根据有多少个唯一名称对结果进行排序:

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

var uniq = names
  .map((name) => {
    return {count: 1, name: name}
  })
  .reduce((a, b) => {
    a[b.name] = (a[b.name] || 0) + b.count
    return a
  }, {})

var sorted = Object.keys(uniq).sort((a, b) => uniq[a] < uniq[b])

console.log(sorted)

Vanilla JS:使用类似 Set 的对象删除重复项

您总是可以尝试将其放入对象中,然后遍历其键:

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"]

使用Underscore.js

这是一个包含用于操纵数组的函数的库。

这是与 jQuery 的晚礼服和 Backbone.js 的吊带一起使用的纽带。

_.uniq

_.uniq(array, [isSorted], [iterator]) 别名: 唯一
产生数组的无重复版本,使用 === 来测试对象的相等性。如果您事先知道数组已排序,则为isSorted传递true将运行更快的算法。如果要基于转换计算唯一项,请传递迭代器函数。

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

alert(_.uniq(names, false));

注意: Lo-Dash下划线竞争对手)还提供了类似的.uniq实现。

使用数组过滤器和 indexOf 函数的单行版本:

arr = arr.filter (function (value, index, array) { 
    return array.indexOf (value) == index;
});

您可以借助filter方法的第二个 - index - 参数,简单地在 JavaScript 中完成此操作:

var a = [2,3,4,5,5,4];
a.filter(function(value, index){ return a.indexOf(value) == index });

或简而言之

a.filter((v,i) => a.indexOf(v) == i)

一条线:

let names = ['Mike','Matt','Nancy','Adam','Jenny','Nancy','Carl', 'Nancy'];
let dup = [...new Set(names)];
console.log(dup);

使用原生 javascript 函数从数组中删除重复项的最简洁方法是使用如下序列:

vals.sort().reduce(function(a, b){ if (b != a[0]) a.unshift(b); return a }, [])

就像我在其他示例中看到的那样,在 reduce 函数中不需要sliceindexOf !不过,将其与过滤器函数一起使用是有意义的:

vals.filter(function(v, i, a){ return i == a.indexOf(v) })

ES6(2015)的另一种已在少数浏览器上运行的方式是:

Array.from(new Set(vals))

甚至使用价差运算符

[...new Set(vals)]

干杯!

像这样使用Array.filter()

var actualArr = ['Apple', 'Apple', 'Banana', 'Mango', 'Strawberry', 'Banana'];

console.log('Actual Array: ' + actualArr);

var filteredArr = actualArr.filter(function(item, index) {
  if (actualArr.indexOf(item) == index)
    return item;
});

console.log('Filtered Array: ' + filteredArr);

在 ES6 中可以将其缩短为

actualArr.filter((item,index,self) => self.indexOf(item)==index);

Array.filter()很好的解释