使用数组合并和删除复杂对象的数组

Merge and Dedupe Array of Complex Objects with Arrays

我有一个非常复杂的问题,我似乎无法弄清楚。我有两个对象数组,我想为其合并分数。它应该 merge/append 基于分数的某些属性。例如,在两个数组之间,总共有 4 个 gameId,其中 3 个是唯一的。合并时,如果 gameId 相同,则应合并 _scores 部分,因此在这种情况下,这将是 EarthNormal 合并。但问题是有时 _scores 中的 score 可能有重复的分数,因此 BAR 和 BASH 几乎看起来完全相同但不同它可以附加但 FOO 分数是两者完全相同,所以我不希望它合并到分数中(如果有意义的话)。

const arr1 = [{
  "gameId": "AirNormal",
  "_scores":
    [{
      "score": 144701,
      "playerName": "FOO",
      "fullCombo": true,
      "timestamp": 1599968866
    }]
}, {
  "gameId": "EarthNormal",
  "_scores":
    [{
      "score": 177352,
      "playerName": "BAR",
      "fullCombo": true,
      "timestamp": 1599969253
    }, {
      "score": 164665,
      "playerName": "FOO",
      "fullCombo": false,
      "timestamp": 1599970971
    }]
}];

const arr2 = [{
  "gameId": "EarthNormal",
  "_scores":
    [{
      "score": 177352,
      "playerName": "BASH",
      "fullCombo": false,
      "timestamp": 1512969017
    }, {
      "score": 164665,
      "playerName": "FOO",
      "fullCombo": false,
      "timestamp": 1599970971
    }]
}, {
  "gameId": "FireNormal",
  "_scores":
    [{
      "_score": 124701,
      "_playerName": "FOO",
      "_fullCombo": true,
      "_timestamp": 1591954866
    }]
}];

我希望最终的合并数组看起来像:

mergedArray = [{
  "gameId": "AirNormal",
  "_scores":
    [{
      "score": 144701,
      "playerName": "FOO",
      "fullCombo": true,
      "timestamp": 1599968866
    }]
}, {
  "gameId": "EarthNormal",
  "_scores":
    [{
      "score": 177352,
      "playerName": "BAR",
      "fullCombo": true,
      "timestamp": 1599969253
    }, {
      "score": 177352,
      "playerName": "BASH",
      "fullCombo": false,
      "timestamp": 1512969017
    }, {
      "score": 164665,
      "playerName": "FOO",
      "fullCombo": false,
      "timestamp": 1599970971
    }]
}, {
  "gameId": "FireNormal",
  "_scores":
    [{
      "score": 124701,
      "playerName": "FOO",
      "fullCombo": true,
      "timestamp": 1591954866
    }]
}]

我试过这样做并使用 lodash:

let merged = [...arr1, ...arr2];

merged = _.uniqBy[merged, 'gameId']
let scoresMerge = _.uniqBy[merged, '_scores']

console.log(scoresMerge);

但它没有像我预期的那样工作。我是不是处理错了?

使用 vanilla javascript.

相当简单
  • 使用解构合并数组
  • reduce() 合并数组到一个由 gameId
  • 索引的对象中
  • 使用 .some() 根据累积的 _scores 数组检查每个 _score 对象的所有属性,如果没有找到匹配则推送。
  • return 使用 Object.values()
  • 缩小对象的值

const arr1 = [{  "gameId": "AirNormal",  "_scores":    [{      "score": 144701,      "playerName": "FOO",      "fullCombo": true,      "timestamp": 1599968866    }]}, {  "gameId": "EarthNormal",  "_scores":    [{      "score": 177352,      "playerName": "BAR",      "fullCombo": true,      "timestamp": 1599969253    }, {      "score": 164665,      "playerName": "FOO",      "fullCombo": false,      "timestamp": 1599970971    }]}];

const arr2 = [{"gameId": "EarthNormal","_scores":[{"score": 177352,"playerName": "BASH","fullCombo": false,"timestamp": 1512969017}, {"score": 164665,"playerName": "FOO","fullCombo": false,"timestamp": 1599970971}]}, {"gameId": "FireNormal","_scores":[{"_score": 124701,"_playerName": "FOO","_fullCombo": true,"_timestamp": 1591954866}]}];


const merged = Object.values([...arr1, ...arr2].reduce((a, {gameId, _scores}) => {
  // retrieve gameId object otherwise initialize it.
  a[gameId] = {...a[gameId] ?? {gameId, _scores: []}};
  // iterate over all _score objects
  _scores.forEach(s => {
    // if accumulator _scores array doesn't have an object matching all properties, push _score 
    if (!a[gameId]['_scores'].some(o => {
        return !Object.entries(s).some(([k, v]) => o[k] !== v)})
      ) {
      a[gameId]['_scores'].push({...s});
    }
  });  
  return a;
}, {}));

console.log(merged);

您需要识别具有相同 gameId 的对象,然后连接并删除它们的 _.scores 数组。

使用 Array.reduce() 和 Map 很容易 concat/dedup 非原始数组项。对于每个项目,您检查请求的键是否已经在地图中。如果不是,则将当前项目分配给地图的键。如果是,则用地图中的项目替换/合并当前项目。

完成对 Map 的迭代后,使用 Array.from() 将 Map 的 .values() 迭代器转换为数组。

const arr1 = [{"gameId":"AirNormal","_scores":[{"score":144701,"playerName":"FOO","fullCombo":true,"timestamp":1599968866}]},{"gameId":"EarthNormal","_scores":[{"score":177352,"playerName":"BAR","fullCombo":true,"timestamp":1599969253},{"score":164665,"playerName":"FOO","fullCombo":false,"timestamp":1599970971}]}];
const arr2 = [{"gameId":"EarthNormal","_scores":[{"score":177352,"playerName":"BASH","fullCombo":false,"timestamp":1512969017},{"score":164665,"playerName":"FOO","fullCombo":false,"timestamp":1599970971}]},{"gameId":"FireNormal","_scores":[{"score":124701,"playerName":"FOO","fullCombo":true,"timestamp":1591954866}]}];

const dedupLastBy = (a1 = [], a2 = [], key) => Array.from(
  [...a1, ...a2].reduce((acc, obj) => {
    const keyName = obj[key];
  
    if(acc.has(keyName)) acc.delete(keyName);
  
    return acc.set(keyName, obj);
  }, new Map()).values()
)

const handleDups = ({ _scores: a, ...o1 }, { _scores: b, ...o2 }) => ({
  ...o1,
  ...o2,
  _scores: dedupLastBy(a, b, 'playerName')
});

const result = Array.from([...arr1, ...arr2]
  .reduce((acc, o) => {
    const { gameId } = o;
    
    if(acc.has(gameId)) acc.set(gameId, handleDups(acc.get(gameId), o));
    else acc.set(gameId, o);
    
    return acc;
  }, new Map()).values());

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>