web-dev-qa-db-fra.com

Comment faire la différence entre deux tableaux d'objets en JavaScript

J'ai deux séries de résultats comme ceci:

// Result 1
[
    { value="4a55eff3-1e0d-4a81-9105-3ddd7521d642", display="Jamsheer" },
    { value="644838b3-604d-4899-8b78-09e4799f586f", display="Muhammed" },
    { value="b6ee537a-375c-45bd-b9d4-4dd84a75041d", display="Ravi" },
    { value="e97339e1-939d-47ab-974c-1b68c9cfb536", display="Ajmal" },
    { value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan" }
]

// Result 2
[
    { value="4a55eff3-1e0d-4a81-9105-3ddd7521d642", display="Jamsheer"},
    { value="644838b3-604d-4899-8b78-09e4799f586f", display="Muhammed"},
    { value="b6ee537a-375c-45bd-b9d4-4dd84a75041d", display="Ravi"},
    { value="e97339e1-939d-47ab-974c-1b68c9cfb536", display="Ajmal"}
]

Le résultat final dont j'ai besoin est la différence entre ces tableaux - le résultat final devrait ressembler à ceci: 

[{ value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan" }]

Est-il possible de faire quelque chose comme ça en JavaScript?

58
BKM

En utilisant uniquement JS natif, quelque chose comme ceci fonctionnera:

a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}]
b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}]

function comparer(otherArray){
  return function(current){
    return otherArray.filter(function(other){
      return other.value == current.value && other.display == current.display
    }).length == 0;
  }
}

var onlyInA = a.filter(comparer(b));
var onlyInB = b.filter(comparer(a));

result = onlyInA.concat(onlyInB);

console.log(result);

97
Cerbrus

Vous pouvez utiliser Array.prototype.filter() en combinaison avec Array.prototype.some() .

Voici un exemple (en supposant que vos tableaux sont stockés dans les variables result1 et result2):

//Find values that are in result1 but not in result2
var uniqueResultOne = result1.filter(function(obj) {
    return !result2.some(function(obj2) {
        return obj.value == obj2.value;
    });
});

//Find values that are in result2 but not in result1
var uniqueResultTwo = result2.filter(function(obj) {
    return !result1.some(function(obj2) {
        return obj.value == obj2.value;
    });
});

//Combine the two arrays of unique entries
var result = uniqueResultOne.concat(uniqueResultTwo);
44
kaspermoerch

J'adopte une approche légèrement plus générale, bien que ses idées soient similaires à celles de @Cerbrus et @Kasper Moerch. Je crée une fonction qui accepte un prédicat pour déterminer si deux objets sont égaux (ici, nous ignorons la propriété $$hashKey, mais ce pourrait être n'importe quoi) et renvoie une fonction qui calcule la différence symétrique de deux listes en fonction de ce prédicat:

a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}]
b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}]

var makeSymmDiffFunc = (function() {
    var contains = function(pred, a, list) {
        var idx = -1, len = list.length;
        while (++idx < len) {if (pred(a, list[idx])) {return true;}}
        return false;
    };
    var complement = function(pred, a, b) {
        return a.filter(function(elem) {return !contains(pred, elem, b);});
    };
    return function(pred) {
        return function(a, b) {
            return complement(pred, a, b).concat(complement(pred, b, a));
        };
    };
}());

var myDiff = makeSymmDiffFunc(function(x, y) {
    return x.value === y.value && x.display === y.display;
});

var result = myDiff(a, b); //=>  {value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan"}

Il a un avantage mineur par rapport à l'approche de Cerebrus (comme le fait l'approche de Kasper Moerch) en ce sens qu'il s'échappe tôt; s'il trouve une correspondance, il ne se soucie pas de vérifier le reste de la liste. Si j’avais une fonction curry, je le ferais un peu différemment, mais cela fonctionne bien.

Explication

Un commentaire demandait une explication plus détaillée pour les débutants. Voici une tentative.

Nous passons la fonction suivante à makeSymmDiffFunc:

function(x, y) {
    return x.value === y.value && x.display === y.display;
}

Cette fonction est la façon dont nous décidons que deux objets sont égaux. Comme toutes les fonctions qui retournent true ou false, on peut l'appeler une "fonction de prédicat", mais ce n'est que de la terminologie. Le point principal est que makeSymmDiffFunc est configuré avec une fonction qui accepte deux objets et renvoie true si nous les considérons comme égaux, false si nous ne le faisons pas.

En utilisant cela, makeSymmDiffFunc (lire "créer une fonction de différence symétrique") nous renvoie une nouvelle fonction:

        return function(a, b) {
            return complement(pred, a, b).concat(complement(pred, b, a));
        };

C'est la fonction que nous allons réellement utiliser. On lui passe deux listes et on trouve les éléments dans la première, pas dans la seconde, puis ceux dans la seconde, pas dans la première et on combine ces deux listes.

En y repensant cependant, j'aurais certainement pu prendre exemple sur votre code et simplifier un peu la fonction principale en utilisant some:

var makeSymmDiffFunc = (function() {
    var complement = function(pred, a, b) {
        return a.filter(function(x) {
            return !b.some(function(y) {return pred(x, y);});
        });
    };
    return function(pred) {
        return function(a, b) {
            return complement(pred, a, b).concat(complement(pred, b, a));
        };
    };
}());

complement utilise le prédicat et renvoie les éléments de sa première liste, pas dans sa seconde. C'est plus simple que mon premier passage avec une fonction contains séparée.

Enfin, la fonction principale est encapsulée dans une expression de fonction immédiatement invoquée (IIFE) afin de conserver la fonction complement interne en dehors de la portée globale.


Mise à jour, quelques années plus tard

Maintenant que ES2015 est devenu assez répandu, je suggérerais la même technique, avec beaucoup moins de passe-partout:

const diffBy = (pred) => (a, b) => a.filter(x => !b.some(y => pred(x, y)))
const makeSymmDiffFunc = (pred) => (a, b) => diffBy(pred)(a, b).concat(diffBy(pred)(b, a))

const myDiff = makeSymmDiffFunc((x, y) => x.value === y.value && x.display === y.display)

const result = myDiff(a, b)
//=>  {value="a63a6f77-c637-454e-abf2-dfb9b543af6c", display="Ryan"}
14
Scott Sauyet
import differenceBy from 'lodash/differenceBy'

const myDifferences = differenceBy(Result1, Result2, 'value')

Cela renverra la différence entre deux tableaux d'objets, en utilisant la clé value pour les comparer. Notez que deux choses avec la même valeur ne seront pas retournées, car les autres clés sont ignorées.

Ceci fait partie de lodash .

4
Noah

Vous pouvez créer un objet avec des clés comme valeur unique correspondant à chaque objet du tableau, puis filtrer chaque tableau en fonction de l'existence de la clé dans l'objet de l'autre. Cela réduit la complexité de l'opération.

ES6

let a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}];
let b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}];

let valuesA = a.reduce((a,{value}) => Object.assign(a, {[value]:value}), {});
let valuesB = b.reduce((a,{value}) => Object.assign(a, {[value]:value}), {});
let result = [...a.filter(({value}) => !valuesB[value]), ...b.filter(({value}) => !valuesA[value])];
console.log(result);

ES5

var a = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal"},  { value:"a63a6f77-c637-454e-abf2-dfb9b543af6c", display:"Ryan"}];
var b = [{ value:"4a55eff3-1e0d-4a81-9105-3ddd7521d642", display:"Jamsheer", $$hashKey:"008"}, { value:"644838b3-604d-4899-8b78-09e4799f586f", display:"Muhammed", $$hashKey:"009"}, { value:"b6ee537a-375c-45bd-b9d4-4dd84a75041d", display:"Ravi", $$hashKey:"00A"}, { value:"e97339e1-939d-47ab-974c-1b68c9cfb536", display:"Ajmal", $$hashKey:"00B"}];

var valuesA = a.reduce(function(a,c){a[c.value] = c.value; return a; }, {});
var valuesB = b.reduce(function(a,c){a[c.value] = c.value; return a; }, {});
var result = a.filter(function(c){ return !valuesB[c.value]}).concat(b.filter(function(c){ return !valuesA[c.value]}));
console.log(result);

4
Nikhil Aggarwal

Je pense que la solution @Cerbrus est parfaite. J'ai implémenté la même solution mais extrait le code répété dans sa propre fonction (DRY). 

 function filterByDifference(array1, array2, compareField) {
  var onlyInA = differenceInFirstArray(array1, array2, compareField);
  var onlyInb = differenceInFirstArray(array2, array1, compareField);
  return onlyInA.concat(onlyInb);
}

function differenceInFirstArray(array1, array2, compareField) {
  return array1.filter(function (current) {
    return array2.filter(function (current_b) {
        return current_b[compareField] === current[compareField];
      }).length == 0;
  });
}
3
Michael

Pour ceux qui aiment les solutions one-liner dans ES6, quelque chose comme ceci:

const arrayOne = [ 
  { value: "4a55eff3-1e0d-4a81-9105-3ddd7521d642", display: "Jamsheer" },
  { value: "644838b3-604d-4899-8b78-09e4799f586f", display: "Muhammed" },
  { value: "b6ee537a-375c-45bd-b9d4-4dd84a75041d", display: "Ravi" },
  { value: "e97339e1-939d-47ab-974c-1b68c9cfb536", display: "Ajmal" },
  { value: "a63a6f77-c637-454e-abf2-dfb9b543af6c", display: "Ryan" },
];
          
const arrayTwo = [
  { value: "4a55eff3-1e0d-4a81-9105-3ddd7521d642", display: "Jamsheer"},
  { value: "644838b3-604d-4899-8b78-09e4799f586f", display: "Muhammed"},
  { value: "b6ee537a-375c-45bd-b9d4-4dd84a75041d", display: "Ravi"},
  { value: "e97339e1-939d-47ab-974c-1b68c9cfb536", display: "Ajmal"},
];

const results = arrayOne.filter(({ value: id1 }) => !arrayTwo.some(({ value: id2 }) => id2 === id1));

console.log(results);
2
atheane

J'ai trouvé cette solution en utilisant un filtre et d'autres.

resultFilter = (firstArray, secondArray) => {
  return firstArray.filter(firstArrayItem =>
    !secondArray.some(
      secondArrayItem => firstArrayItem._user === secondArrayItem._user
    )
  );
};

0
Gorez Tony

La plupart des réponses ici sont plutôt complexes, mais la logique derrière cela n’est-elle pas assez simple? 

  1. vérifie quel tableau est le plus long et le fournit comme premier paramètre (si la longueur est égale, l'ordre des paramètres n'a pas d'importance)
  2. Itérer sur array1.
  3. Pour l'élément d'itération en cours de array1, vérifiez s'il est présent dans array2
  4. S'il n'est pas présent, alors
  5. Poussez-le vers le tableau 'différence'
const getArraysDifference = (longerArray, array2) => {
  const difference = [];

  longerArray.forEach(el1 => {      /*1*/
    el1IsPresentInArr2 = array2.some(el2 => el2.value === el1.value); /*2*/

    if (!el1IsPresentInArr2) { /*3*/
      difference.Push(el1);    /*4*/
    }
  });

  return difference;
}

O (n ^ 2) complexité.

0
azrahel

J'ai créé un diff généralisé qui compare 2 objets de tout type et peut exécuter un gestionnaire de modifications Gist.github.com/bortunac "diff.js" Un exemple d'utilisation:

old_obj={a:1,b:2,c:[1,2]}
now_obj={a:2 , c:[1,3,5],d:55}

donc la propriété a est modifiée, b est supprimée, c modifiée, d est ajoutée

var handler=function(type,pointer){
console.log(type,pointer,this.old.point(pointer)," | ",this.now.point(pointer)); 

}

maintenant utiliser comme

df=new diff();
df.analize(now_obj,old_obj);
df.react(handler);

la console montrera

mdf ["a"]  1 | 2 
mdf ["c", "1"]  2 | 3 
add ["c", "2"]  undefined | 5 
add ["d"]  undefined | 55 
del ["b"]  2 | undefined 
0
bortunac