JavaScript - 合并两个对象数组并根据属性值删除重复数据

JavaScript - merge two arrays of objects and de-duplicate based on property value

本文关键字:属性 删除 数据 数组 合并 对象 两个 JavaScript      更新时间:2023-09-26

我想用另一个数组中的对象更新(替换(数组中的对象。每个对象具有相同的结构。例如

var origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
var updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];
// desired result:
NEWArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];

我已经尝试了concat((和Underscore的_.uniq函数,但它总是转储较新的对象并返回原始数组。

有没有办法用updatingArr中的对象覆盖(替换(origArr - 在name属性上匹配?

我来到这里寻找这个,看到了@Gruff兔子的技术,想知道"lodash"是否甚至不是"下划线"的更好选择?

你瞧:

let result = _.unionBy(updatingArr, origArr, 'name');
您可以将

Array#mapArray#reduce结合使用

var origArr = [{ name: 'Trump', isRunning: true }, { name: 'Cruz', isRunning: true }, { name: 'Kasich', isRunning: true }],
    updatingArr = [{ name: 'Cruz', isRunning: false }, { name: 'Kasich', isRunning: false }],
    NEWArr = origArr.map(function (a) {
        return this[a.name] || a;
    }, updatingArr.reduce(function (r, a) {
        r[a.name] = a;
        return r;
    }, Object.create(null)));
document.write('<pre>' + JSON.stringify(NEWArr, 0, 4) + '</pre>');

2022 年更新

使用具有 name 的对象作为哈希,并通过从哈希表或原始对象获取更新来映射原始数组。

const
    origArr = [{ name: 'Trump', isRunning: true }, { name: 'Cruz', isRunning: true }, { name: 'Kasich', isRunning: true }],
    updatingArr = [{ name: 'Cruz', isRunning: false }, { name: 'Kasich', isRunning: false }],
    updates = Object.fromEntries(updatingArr.map(o => [o.name, o])),
    result = origArr.map(o => updates[o.name] || o);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

使用Map的另一种方法。

此方法也适用于仅在更新数组中的对象。

const
    origArr = [{ name: 'Trump', isRunning: true }, { name: 'Cruz', isRunning: true }, { name: 'Kasich', isRunning: true }],
    updatingArr = [{ name: 'Cruz', isRunning: false }, { name: 'Kasich', isRunning: false }],
    result = Array.from([...origArr, ...updatingArr]
        .reduce((m, o) => m.set(o.name, o), new Map)
        .values()
    );
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

使用双 for 循环和拼接,您可以这样做:

for(var i = 0, l = origArr.length; i < l; i++) {
    for(var j = 0, ll = updatingArr.length; j < ll; j++) {
        if(origArr[i].name === updatingArr[j].name) {
            origArr.splice(i, 1, updatingArr[j]);
            break;
        }
    }
}

这里的例子

const origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
const updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];
let hash = {};
for(let i of origArr.concat(updatingArr)) {
  if(!hash[i]) {
    hash[i.name] = i;
  }
}
let newArr = [];
for(let i in hash) {
  newArr.push(hash[i])
}
console.log(newArr);
您可以使用

哈希值,该哈希值按名称提供索引,并Object.assign更新。

var hash = origArr.reduce(function(hash, obj, index) {
  hash[obj.name] = index;
  return hash;
}, Object.create(null));
for(var obj of updatingArr) {
  Object.assign(origArr[hash[obj.name]], obj);
}

你可以试一试。

var origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
var updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];
var origLength = origArr.length;
var updatingLength = updatingArr.length;
//Traverse the original array and replace only if the second array also has the same value
for(i = origLength-1; i >= 0; i--) {
    for(j = updatingLength -1; j >= 0; j--) {
    if(origArr[i].name === updatingArr[j].name) {
        origArr[i] = updatingArr[j];
    }
  }
}
console.log(origArr);

下面是使用下划线的解决方案:

var result = _.map(origArr, function(orig){
    return _.extend(orig, _.findWhere(updatingArr, {name: orig.name}));
});

此版本允许您定义将对象定义为重复对象的selector

  • forEach 迭代新数据
  • findIndex 返回一个索引>= 0如果两个选择器相等。如果没有一个相等,则返回-1
  • 如果有重复项,我们使用切片将原始的替换为新的。
  • 如果没有重复项,我们将其推送到原始数组中。

const origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
const updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];
const mergeArrayOfObjects = (original, newdata, selector = 'key') => {
	newdata.forEach(dat => {
		const foundIndex = original.findIndex(ori => ori[selector] == dat[selector]);
		if (foundIndex >= 0) original.splice(foundIndex, 1, dat);
        else original.push(dat);
	});
	return original;
};
const result = mergeArrayOfObjects(origArr, updatingArr, "name")
console.log('RESULT -->', result)

在 ES6 中,你可以像这样使用对象 Map...

let map = new Map();
let origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
let updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];
// Concating arrays with duplicates
let NEWArr = origArr.concat(updatingArr);
// Removing duplicates items
NEWArr.forEach(item => {
  if(!map.has(item.name)){
    map.set(item.name, item);
  }
});
Array.from(map.values());

请记住:Map 对象需要唯一键,在这种情况下我使用了 name .

这将满足您的需求:

var origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
var updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];
for (var i = 0; i < updatingArr.length; ++i) {
  var updateItem = updatingArr[i];
  for (var j = 0; j < origArr.length; ++j) {
    var origItem = origArr[j];
    if (origItem.name == updateItem.name) {
      origItem.isRunning = updateItem.isRunning;
      break;    
    }
  }
}
document.write('<pre>' + JSON.stringify(origArr, 0, 4) + '</pre>');

与@gevorg答案相同,但是如果没有找到匹配项,您可能还希望向原始数组添加新对象。

let combinedEvents = origEvents;
for(let i =0; i< newEvents.length; i++){
  let newEvent = newEvents[i];
  for(let j =0; j< origEvents.length; j++){
    let origEvent = origEvents[j];
    if(newEvent.events_id == origEvent.events_id){
      combinedEvents.splice(j,1, newEvent);
      break;
    } else if(j === origEvents.length - 1){
      combinedEvents.push(newEvent);
      break;
    }
  }
}

Backbone的集合为这样的场景做好了充分的准备。

首先,我们定义一个知道查找name属性的模型类型:

import { Model } from 'backbone';
const President = Model.extend({ idAttribute: 'name' });

然后,我们将初始数组放在使用上述模型的集合中:

import { Collection } from 'backbone';
const presidents = new Collection(origArr, { model: President });

现在,我们可以根据需要随时运行presidents.set,它将新表示与旧值合并:

presidents.set(updateArr);
presidents.get('Cruz').get('isRunning'); // false
presidents.toJSON(); // NEWArr

将它们放在一个可运行的片段中:

const { Model, Collection } = Backbone;
const President = Model.extend({ idAttribute: 'name' });
const presidents = new Collection([
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
], { model: President });
presidents.set([
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
]);
console.log(presidents.get('Cruz').get('isRunning'));
console.log(presidents.toJSON());
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
<script src="https://backbonejs.org/backbone-min.js"></script>

虽然下划线没有与 Lodash unionBy直接等价物,但你仍然可以使用 concatgroupBymaplast 在高效的 oneliner 中获得相同的结果:

import { groupBy, map, last } from 'underscore';
var NEWArr = map(groupBy(origArr.concat(updateArr), 'name'), last);

等效的,稍长但更干净的符号使用chain

import { chain } from 'underscore';
var NEWArr = chain(origArr).concat(updateArr).groupBy('name').map(last).value();

实际上,您可以使用上述配方定义自己的unionBy并将其添加到"自己下划线"中:

import _, { groupBy, map, first } from 'underscore';
function unionBy(...args) {
    const iteratee = _.iteratee(args.pop());
    const candidates = [].concat.apply(null, args);
    return map(groupBy(candidates, iteratee), first);
}
_.mixin({ unionBy }); // unionBy can now be used in chaining

请注意,我们现在使用 first 而不是 last ,因为 Lodash unionBy的语义决定了第一次出现获胜。

使用 ES-6 数据结构尝试此方法: const result = [...new Set([...origArr, ...updatingArr])]