在Javascript中删除对象数组中的重复项

32

我有一个对象数组

list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}]

我正在寻找一种高效的方法(如果可能,O(log(n))),以去除重复项并最终得到

list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}]

我已经尝试过_.uniq甚至_.contains,但却找不到令人满意的解决方案。

谢谢!

编辑:这个问题已被识别为另一个问题的重复。在发布之前,我看到了这个问题,但它并没有回答我的问题,因为它是一个对象数组(而不是一个二维数组,感谢Aaron),或者至少其他问题的解决方案在我的情况下并不起作用。


1
可能是从JavaScript数组中删除重复项的重复问题。 - Rajesh
3
请注意,这不是一个二维数组,而是一个对象数组。 - Aaron
10个回答

40
使用 Set 的普通 JavaScript (ES2015):

Set 是一个内置的 JavaScript 对象,它允许你存储不同类型的值,而且每个值只会出现一次。通过使用 Set ,你可以轻松地添加、删除和检查值是否存在于集合中。

const list = [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }, { x: 1, y: 2 }];

const uniq = new Set(list.map(e => JSON.stringify(e)));

const res = Array.from(uniq).map(e => JSON.parse(e));

document.write(JSON.stringify(res));


4
“stringify”和“parse”看起来不是实现这个功能最高效的方式。 - calbertts
@AminJafari,即使在2020年,使用var也没有任何问题。只要作用域得到处理,var就绝对没问题。虽然这个答案不是最高效的,但它可以按照OP的问题工作。 - SeaWarrior404
1
@SeaWarrior404 我知道,这就是为什么我点了赞 :) 只是在可以使用 const 的地方使用 var 不是一个好习惯。 - Amin Jafari
JSON.stringify 是一种正确、通用的方法,用于识别内容相同但不同对象。然而,大多数使用 JSON.stringify 的答案(包括这个)只有在键总是以相同顺序指定时才有效。请参阅我的答案,了解如何使用替换函数,以便即使键的顺序变化,解决方案也能正常工作:https://dev59.com/EloV5IYBdhLWcg3wmP3p#71102751。 - Julian

32

尝试使用以下方法:

list = list.filter((elem, index, self) => self.findIndex(
    (t) => {return (t.x === elem.x && t.y === elem.y)}) === index)

2
完美的。谢谢。 - Lorena Pita
只是一个问题,你不能仅使用Object.is()来进行筛选比较吗?https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is - JaeGeeTee
你好!我以前从未见过像 .filter((elem, index, self) => 这样使用 self 的情况,它指的是什么?我知道 element 指的是元素值,index 指的是数组中的位置。或者如果有人能够指引我查看文档,那就太好了! - Faith

16

纯JS版本:

const list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];

function dedupe(arr) {
  return arr.reduce(function(p, c) {

    // create an identifying id from the object values
    var id = [c.x, c.y].join('|');

    // if the id is not found in the temp array
    // add the object to the output array
    // and add the key to the temp array
    if (p.temp.indexOf(id) === -1) {
      p.out.push(c);
      p.temp.push(id);
    }
    return p;

    // return the deduped array
  }, {
    temp: [],
    out: []
  }).out;
}

console.log(dedupe(list));


14

我会使用Arrayr.prototype.reduceArrayr.prototype.some方法的组合,加上展开运算符。

1. 明确的解决方案。基于对数组对象包含内容的完全了解。

list = list.reduce((r, i) => 
  !r.some(j => i.x === j.x && i.y === j.y) ? [...r, i] : r
, [])

这里对比对象的结构有严格限制:{x:N, y:M}。而[{x:1, y:2}, {x:1, y:2, z:3}] 将被过滤为 [{x:1, y:2}]

2. 通用解决方案,JSON.stringify()。被比较的对象可以有任意数量的任意属性。

list = list.reduce((r, i) => 
  !r.some(j => JSON.stringify(i) === JSON.stringify(j)) ? [...r, i] : r
, [])

这种方法对属性排序有限制,因此[{x:1, y:2}, {y:2, x:1}]将无法过滤。

3. 通用解决方案,Object.keys()。顺序不重要。

list = list.reduce((r, i) => 
  !r.some(j => !Object.keys(i).some(k => i[k] !== j[k])) ? [...r, i] : r
, [])

这种方法还有另一个限制:相比较的对象必须具有相同的键列表。因此,即使它们显然不同,[{x:1, y:2}, {x:1}] 也将被过滤。

4. 通用解决方案,Object.keys() + .length

list = list.reduce((r, i) => 
  !r.some(j => Object.keys(i).length === Object.keys(j).length 
    && !Object.keys(i).some(k => i[k] !== j[k])) ? [...r, i] : r
, [])

使用最后一种方法,将通过键数、键本身和键值进行对象比较。

我创建了一个Plunker来进行演示。


你能否解释一下第四种方法中发生的事情,特别是数组部分和一些比较?有点难以理解。 - SeaWarrior404
@SeaWarrior404 4种方法是将键/值比较与长度比较相结合,其中长度比较作为第一个“some”呈现,迭代地比较初始数组中每个对象的键的长度与初始数组中所有其他对象的键的长度,以确保列表中的所有项目具有相同数量的字段。如果给定对象与列表中的其他对象具有相同数量的字段,则将其发送到键值比较(第二个“some”)。如果不是,则会立即被过滤掉。 - dhilt
在每次迭代中,您无需重新创建一个数组。您可以使用r.push(i)来代替。如果!r.some(j => JSON.stringify(i) === JSON.stringify(j)),则将i添加到r中。最后返回r - abumalick
非常好的通用解决方案,正是我需要的! - stackato

6

ES6+中的一行代码技巧

如果要按照x和y查找唯一值:

arr.filter((v,i,a)=>a.findIndex(t=>(t.x === v.x && t.y===v.y))===i)

如果您想根据所有属性查找唯一项:

arr.filter((v,i,a)=>a.findIndex(t=>(JSON.stringify(t) === JSON.stringify(v)))===i)

4
以下内容可行:
var a = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];

var b = _.uniq(a, function(v) { 
    return v.x && v.y;
})

console.log(b);  // [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 } ]

1
谢谢!这对我来说可行,是否比安迪的解决方案更快? - kwn
2
除非你正在处理成千上万个元素,否则性能不应该是一个问题。我想你必须权衡加载单独的库的成本(如果lodash还没有在你的堆栈中),或者只是使用稍微冗长一些的原生函数。 - Andy
1
不要认为这个代码能正常工作,例如 1 && 2 == 2 && 2。 - Gruff Bunny
你的比较与@GruffBunny的答案完全无关。 - baao
1
如果输入是 [{x:1, y: 2}, {x: 2. y:2}],那么结果将是 [{x:1, y:2}] - 即使第二个对象不是重复的,它也会被删除。 - Gruff Bunny
显示剩余5条评论

4
在O(n)的时间复杂度下,检查临时对象中是否已经存在,然后过滤数组。

var list = [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }, { x: 1, y: 2 }],
    filtered = function (array) {
        var o = {};
        return array.filter(function (a) {
            var k = a.x + '|' + a.y;
            if (!o[k]) {
                o[k] = true;
                return true;
            }
        });
    }(list);

document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');


0

不需要引用任何库,适用于任何深度。

限制:

  • 您必须仅提供字符串或数字属性作为哈希对象,否则会得到不一致的结果
/** 
 * Implementation, you can convert this function to the prototype pattern to allow
 * usage like `myArray.unique(...)`
 */ 
function unique(array, f) {
  return Object.values(
    array.reduce((acc, item) => ({ ...acc, [f(item).join(``)]: item }), {})
  );
}

const list = [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}, { x: 1, y: 2}];

// Usage
const result = unique(list, item => [item.x, item.y]);

// Output: [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}]
console.log(result); 

片段示例

// Implementation
function unique(array, f) {
  return Object.values(
    array.reduce((acc, item) => ({ ...acc, [f(item).join(``)]: item }), {})
  );
}

// Your object list
const list = [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}, { x: 1, y: 2}];

// Usage
const result = unique(list, item => [item.x, item.y]);

// Add result to DOM
document.querySelector(`p`).textContent = JSON.stringify(result, null, 2);
<p></p>


0

使用Underscore的_.uniq和标准的JSON.stringify,它可以成为一行代码:

var list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];

var deduped = _.uniq(list, JSON.stringify);

console.log(deduped);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>

然而,这假定键总是以相同的顺序指定。通过复杂化迭代器,我们可以使解决方案即使键的顺序变化也能正常工作。这个问题以及解决方案也适用于涉及 JSON.stringify 的其他答案。

var list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {y:2, x:1}];

// Ensure that objects are always stringified
// with the keys in alphabetical order.
function replacer(key, value) {
    if (!_.isObject(value)) return value;
    var sortedKeys = _.keys(value).sort();
    return _.pick(value, sortedKeys);
}

// Create a modified JSON.stringify that always
// uses the above replacer.
var stringify = _.partial(JSON.stringify, _, replacer, null);

var deduped = _.uniq(list, stringify);

console.log(deduped);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>

对于 Lodash 4,使用 _.uniqBy 替代 _.uniq


-2

使用lodash,您可以使用这个一行代码:

 _.uniqBy(list, e => { return e.x && e.y })

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接