Объединение и дедупликация массива сложных объектов с помощью массивов

#javascript #arrays #ecmascript-6 #lodash

#javascript #массивы #ecmascript-6 #Lodash

Вопрос:

У меня довольно сложная проблема, которую я, похоже, не могу понять. У меня есть два массива объектов, для которых я хотел бы объединить оценки. Он должен объединять / добавлять определенные свойства на основе оценок. Например, между двумя массивами всего 4 gameId , причем 3 из них уникальны. При слиянии он должен объединять _scores раздел, если он одинаковый gameId , поэтому в этом случае это будет как EarthNormal слияние. Но проблема в том, что иногда у score in _scores могут быть повторяющиеся оценки, поэтому 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);
 

но это сработало не так, как я ожидал. Я неправильно подхожу к этому?

Ответ №1:

Это довольно просто с использованием ванильного javascript.

  • объединение массивов с помощью деструктурирования
  • reduce() объединенные массивы в объект, индексируемый gameId
  • проверьте все свойства каждого _score объекта по сравнению с накопленным _scores массивом с помощью .some() и нажмите, если совпадение не найдено.
  • возвращает значения уменьшенного объекта с помощью 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); 

Ответ №2:

Вам нужно идентифицировать объекты с одинаковыми gameId , а затем объединить и дедуплицировать их _.scores массив.

Легко объединить / дедуплицировать элементы массива, не являющиеся примитивными, используя Array.reduce() и карту. Для каждого элемента вы проверяете, есть ли запрошенный ключ уже на карте. Если это не так, вы присваиваете текущий элемент ключу карты. Если это вы, замените / объедините текущий элемент с элементом на карте.

После завершения итерации карты используйте Array.from() для преобразования .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>