在JavaScript数组中收集唯一对象

6
假设我有以下的对象数组:
var firstDataSet = [
  {'id': 123, 'name': 'ABC'},
  {'id': 456, 'name': 'DEF'},
  {'id': 789, 'name': 'GHI'},
  {'id': 101, 'name': 'JKL'}
];

var secondDataSet = [
  {'id': 123, 'name': 'ABC', 'xProp': '1q'},
  {'id': 156, 'name': 'MNO', 'xProp': '2w'},
  {'id': 789, 'name': 'GHI', 'xProp': '3e'},
  {'id': 111, 'name': 'PQR', 'xProp': '4r'}
];

现在我想收集具有唯一对象的数组(匹配idname),即:
var firstDataSet = [
  {'id': 123, 'name': 'ABC', 'xProp': '1q'},
  {'id': 456, 'name': 'DEF'},
  {'id': 789, 'name': 'GHI', 'xProp': '3e'},
  {'id': 101, 'name': 'JKL'},
  {'id': 156, 'name': 'MNO', 'xProp': '2w'},
  {'id': 111, 'name': 'PQR', 'xProp': '4r'}
];

我可以使用 ALL 来收集所有内容。
Array.prototype.unshift.apply(firstDataSet , secondDataSet );

但我不确定如何过滤掉重复项。有什么建议吗?

编辑:我的两个不同数组中的对象并不相同。至少在属性数量上。


可能是如何在JavaScript中检查数组是否包含对象?的重复。 - Heretic Monkey
请移步 https://dev59.com/U3E95IYBdhLWcg3wp_kg,该链接为JavaScript中从对象数组中删除重复项的相关问题。 - Heretic Monkey
你是否愿意使用underscore或lodash? - omarjmh
@Omarjmh 当然可以,我可以使用lodash。 - αƞjiβ
然后尝试使用它。展示给我们你尝试过的东西。 - omarjmh
4个回答

10

去除所有具有相同属性的重复项

这是原始问题.

使用Set

Set 对象可以存储任意类型的唯一值,包括原始类型和对象引用。

您还可以使用对象字面量。

var list = [JSON.stringify({id: 123, 'name': 'ABC'}), JSON.stringify({id: 123, 'name': 'ABC'})]; 
var unique_list = new Set(list); // returns Set {"{'id': 123, 'name': 'ABC'}"}
var list = Array.from(unique_list); // converts back to an array, and you can unstringify the results accordingly.

如需了解更多将集合转换为数组的方法,请参考此处的说明。 如果您不能使用定义了Set的ES6,则旧版浏览器可以使用polyfill


删除具有重复属性子集的对象

不幸的是,这些对象不再是严格的重复项,并且不能像使用Set那样友好地处理。

解决这种问题的最简单方法是遍历对象数组,识别出具有重复属性值的对象,并直接使用例如splice消除它们。


如果他不能使用 ES6 呢? - omarjmh
我更新了我的答案,指出这只适用于ES6。许多浏览器已经基本支持ES6。Set有一个填充程序 - Akshat Mahajan
如果您使用 firstDataSet.concat(secondDataSet),这将创建一个新的数组而不会取消原始数组,因此您可能还需要 firstDataSet = secondDataSet = null - frajk
抱歉,我一开始没有说清楚。我在第二个数组中的对象有一些额外的属性,所以我无法使用Set。 - αƞjiβ
我点赞是因为你为这个问题所做的所有工作。 - omarjmh
显示剩余10条评论

1
这可以通过扩展Set类来实现,如下所示。
    var firstDataSet = [
      {'id': 123, 'name': 'ABC'},
      {'id': 456, 'name': 'DEF'},
      {'id': 789, 'name': 'GHI'},
      {'id': 101, 'name': 'JKL'}
    ];

    var secondDataSet = [
      {'id': 123, 'name': 'ABC', 'xProp': '1q'},
      {'id': 156, 'name': 'MNO', 'xProp': '2w'},
      {'id': 789, 'name': 'GHI', 'xProp': '3e'},
      {'id': 111, 'name': 'PQR', 'xProp': '4r'}
    ];

    Array.prototype.unshift.apply(firstDataSet , secondDataSet );

    //console.log(firstDataSet)

    class UniqueSet extends Set {
            constructor(values) {
                super(values);

                const data = [];
                for (let value of this) {
                    if (data.includes(JSON.parse(value.id))) {
                        this.delete(value);
                    } else {
                        data.push(value.id);
                    }
                }
            }
          }

console.log(new UniqueSet(firstDataSet))

工作中 link


0
这可能不是最有效的解决方案,但假设id始终是唯一的,它应该可以工作。
var firstDataSet = [
  {'id': 123, 'name': 'ABC'},
  {'id': 456, 'name': 'DEF'},
  {'id': 789, 'name': 'GHI'},
  {'id': 101, 'name': 'JKL'}
];

var secondDataSet = [
  {'id': 123, 'name': 'ABC', 'xProp': '1q'},
  {'id': 156, 'name': 'MNO', 'xProp': '2w'},
  {'id': 789, 'name': 'GHI', 'xProp': '3e'},
  {'id': 111, 'name': 'PQR', 'xProp': '4r'}
];

Array.prototype.unique = function() {
    var o = {}, i, l = this.length, r = [];
    for(i=0; i<l;i+=1) o[this[i]] = this[i];
    for(i in o) r.push(o[i]);
    return r;
};

function concatUnique(a, b, property) {
    var arr = a.concat(b);
    arr.sort(function(a,b) { 
        return Object.keys(b).length - Object.keys(a).length; 
    });
    var ids = arr.map(function(obj){ return obj[property] }).unique();

    return arr.filter(function(obj) { 
        if(ids.indexOf(obj[property]) > -1) { 
            ids.splice( ids.indexOf(obj[property]) , 1); 
            return true; 
        } else { 
            return false 
        }
    });
}

var newArray = concatUnique(firstDataSet, secondDataSet, 'id');

这似乎没有按照 OP 的要求,将第二个数据集中 123 条目中的 xProp: '1q' 属性添加到第一个数据集中相应的条目中。 - user663031
@torazaburo 哦,天啊,你说得对。已经修复了。有点。这个答案将适用于 OP 的示例数据,只需保留具有更多属性的重复对象,但如果有相同数量但不同属性的重复对象,例如,它将无法工作。 - frajk

0

我们将使用concat组合这两个数组,然后使用filter过滤结果数组。对于每个元素,我们将使用findIndex查找具有相同id和name的第一个元素的索引。如果该索引与当前索引相同,则意味着这是该id和name的第一次出现,因此我们只需让它通过即可。否则,我们将添加新字段到第一次出现的位置,并将其过滤掉。

function combine(a1, a2) {

  function match(e1, e2) { return e1.id === e2.id && e1.name === e2.name); }

  return a1.concat(a2) . filter((e1, i, a) => {
    let firstIndex = a.findIndex(e2 => match(e1, e2));
    if (i === firstIndex) return true; // this is the first occurrence
    a[firstIndex].xProp = e2.xProp;    // copy over property
    return false;                      // filter out
  });

}

如果您想处理任意属性,而不仅仅是xProp,那么请将相关行更改为类似以下的内容。
a[firstIndex] = Object.assign(e2, a[firstIndex]);

这将用当前出现的所有属性及其可能具有的任何其他属性的副本结果替换第一次出现。

强制性免责声明:与往常一样,根据您的环境,您可能没有箭头函数、Array#findIndexObject.assign。在这种情况下,根据需要进行重写/填充/转译。


我很想听听您对我的方法和您的方法之间的区别的看法,忽略明显的区别,即您的方法检查两个属性,而我的方法只检查一个属性。但是,如果我们两个的方法都只检查一个属性或两个属性,它们之间的性能或其他值得注意的差异是什么? - frajk

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