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

104

J'ai deux ensembles de résultats comme celui-ci:

// Result 1
[
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" },
    { value: "4", display: "Ryan" }
]

// Result 2
[
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" },
]

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

[{ value: "4", display: "Ryan" }]

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

BKM
la source
Donc, vous voulez un tableau de tous les éléments qui n'apparaissent pas dans les deux tableaux, filtrés par valeur et affichage?
Cerbrus
Je veux la différence entre les deux tableaux. La valeur sera présente dans n'importe lequel des tableaux.
BKM
2
Il ressemble au tableau tel qu'affiché lors de la connexion à la console Firebug ...
Mike C
2
Désolé, mais l'objet json est faux ... vous devez changer = pour:
Walter Zalazar

Réponses:

141

En utilisant uniquement JS natif, quelque chose comme ça 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);

Cerbrus
la source
1
Cela fonctionne, et c'est peut-être la meilleure réponse directe, mais ce serait bien de le transformer en quelque chose qui accepte un prédicat et deux listes et renvoie la différence symétrique des deux listes en appliquant le prédicat de manière appropriée. (Des points supplémentaires si c'était plus efficace que d'avoir à parcourir toutes les comparaisons m * n deux fois!)
Scott Sauyet
@ScottSauyet: Je ne connais pas le terme «prédicat» (pas un anglophone). Est-ce cela return a.value ===...dans votre réponse? (Belle solution, au fait, +1) Mis à part l'utilisation Array.prototype.some(), je ne peux pas vraiment trouver un moyen plus efficace / plus court de le faire.
Cerbrus
2
@Cerbrus: Oui. Un prédicat est une fonction qui renvoie un booléen ( trueou une falsevaleur.) Dans ce cas, si nous séparons la notion de test d'égalité du reste du code en demandant à l'utilisateur de passer le contrôle d'égalité en tant que fonction, nous pouvons faire un algorithme générique simple.
Scott Sauyet
@ScottSauyet: Il m'a fallu du temps pour retrouver cette réponse, mais maintenant c'est un peu mieux: D
Cerbrus
ES6 const comparer = (otherArray) => (current) => otherArray.filter ((other) => other.value == current.value && other.display == current.display) .length == 0;
Shnigi
70

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 result1et 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);
kaspermoerch
la source
43

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);

atheane
la source
3
Expliquez-le s'il vous plaît!
Bruno Brito
15

J'adopte une approche légèrement plus générale, bien que des idées similaires aux approches 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 $$hashKeypropriété, mais cela pourrait être n'importe quoi) et retourne 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 sur l'approche de Cerebrus (tout comme l'approche de Kasper Moerch) en ce qu'il s'échappe tôt; s'il trouve une correspondance, il ne prend pas la peine de vérifier le reste de la liste. Si j'avais uncurry fonction à portée de main, je le ferais un peu différemment, mais cela fonctionne très bien.

Explication

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

On passe 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 trueou false, cela peut être appelé une "fonction de prédicat", mais ce n'est que de la terminologie. Le point principal est qu'il makeSymmDiffFuncest configuré avec une fonction qui accepte deux objets et retourne truesi nous les considérons égaux, falsesinon.

En utilisant cela, makeSymmDiffFunc(lire "faire 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 utiliserons réellement. On lui passe deux listes et il 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, j'aurais certainement pu s'inspirer de 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));
        };
    };
}());

complementutilise 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 containsfonction séparée .

Enfin, la fonction principale est enveloppée dans une expression de fonction immédiatement appelée ( IIFE ) pour garder la complementfonction interne hors de la portée globale.


Mise à jour, quelques années plus tard

Maintenant que ES2015 est devenu assez omniprésent, je suggère 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"}
Scott Sauyet
la source
1
Pourriez-vous ajouter plus d'explications à votre code? Je ne suis pas sûr qu'un débutant en JavaScript comprendrait comment fonctionne l'approche par prédicat.
kaspermoerch
1
@KasperMoerch: Ajout d'une longue explication. J'espère que cela aide. (Cela m'a également fait reconnaître un nettoyage sérieux que ce code devrait avoir.)
Scott Sauyet
9
import differenceBy from 'lodash/differenceBy'

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

Cela retournera la différence entre deux tableaux d'objets, en utilisant la clé valuepour 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 .

Noé
la source
L'objet json ci-dessus est incorrect. Quand essayez de cette façon change = pour:
Walter Zalazar
7

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 d'un 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);

Nikhil Aggarwal
la source
5

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;
  });
}
Michael
la source
2

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

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

Gorez Tony
la source
1

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

  1. vérifier quel tableau est le plus long et le fournir 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 actuel de array1, vérifiez s'il est présent dans array2
  4. S'il n'est PAS présent, que
  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;
}

Complexité O (n ^ 2).

Azrahel
la source
plus 1 pour l'inclusion de complexité
delavago1999
1

vous pouvez faire diff a sur b et diff b sur a, puis fusionner les deux résultats

let a = [
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" },
    { value: "4", display: "Ryan" }
]

let b = [
    { value: "0", display: "Jamsheer" },
    { value: "1", display: "Muhammed" },
    { value: "2", display: "Ravi" },
    { value: "3", display: "Ajmal" }
]

// b diff a
let resultA = b.filter(elm => !a.map(elm => JSON.stringify(elm)).includes(JSON.stringify(elm)));

// a diff b
let resultB = a.filter(elm => !b.map(elm => JSON.stringify(elm)).includes(JSON.stringify(elm)));  

// show merge 
console.log([...resultA, ...resultB]);

lemospy
la source
0

J'ai fait un diff généralisé qui compare 2 objets de tout type et peut exécuter un gestionnaire de modification 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é

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 
Bortunac
la source
0

La manière la plus générique et la plus simple:

findObject(listOfObjects, objectToSearch) {
    let found = false, matchingKeys = 0;
    for(let object of listOfObjects) {
        found = false;
        matchingKeys = 0;
        for(let key of Object.keys(object)) {
            if(object[key]==objectToSearch[key]) matchingKeys++;
        }
        if(matchingKeys==Object.keys(object).length) {
            found = true;
            break;
        }
    }
    return found;
}

get_removed_list_of_objects(old_array, new_array) {
    // console.log('old:',old_array);
    // console.log('new:',new_array);
    let foundList = [];
    for(let object of old_array) {
        if(!this.findObject(new_array, object)) foundList.push(object);
    }
    return foundList;
}

get_added_list_of_objects(old_array, new_array) {
    let foundList = [];
    for(let object of new_array) {
        if(!this.findObject(old_array, object)) foundList.push(object);
    }
    return foundList;
}
Pulin Jhaveri
la source
0

Je préfère les objets cartographiques lorsqu'il s'agit de grands tableaux.

// create tow arrays
array1 = Array.from({length: 400},() => ({value:Math.floor(Math.random() * 4000)}))
array2 = Array.from({length: 400},() => ({value:Math.floor(Math.random() * 4000)}))

// calc diff with some function
console.time('diff with some');
results = array2.filter(({ value: id1 }) => array1.some(({ value: id2 }) => id2 === id1));
console.log('diff results ',results.length)
console.timeEnd('diff with some');

// calc diff with map object
console.time('diff with map');
array1Map = {};
for(const item1 of array1){
    array1Map[item1.value] = true;
}
results = array2.filter(({ value: id2 }) => array1Map[id2]);
console.log('map results ',results.length)
console.timeEnd('diff with map');

yoni12ab
la source
0

JavaScript a des cartes, qui fournissent le temps d'insertion et de recherche O (1). Par conséquent, cela peut être résolu en O (n) (et non en O (n²) comme le font toutes les autres réponses). Pour cela, il est nécessaire de générer une clé primitive unique (chaîne / numéro) pour chaque objet. On pourrait JSON.stringify, mais c'est assez sujet aux erreurs car l'ordre des éléments pourrait influencer l'égalité:

 JSON.stringify({ a: 1, b: 2 }) !== JSON.stringify({ b: 2, a: 1 })

Par conséquent, je prendrais un délimiteur qui n'apparaît dans aucune des valeurs et composerais une chaîne manuellement:

const toHash = value => value.value + "@" + value.display;

Ensuite, une carte est créée. Lorsqu'un élément existe déjà dans la carte, il est supprimé, sinon il est ajouté. Par conséquent, seuls les éléments qui sont inclus les temps impairs (c'est-à-dire une seule fois) restent. Cela ne fonctionnera que si les éléments sont uniques dans chaque tableau:

const entries = new Map();

for(const el of [...firstArray, ...secondArray]) {
  const key = toHash(el);
  if(entries.has(key)) {
    entries.delete(key);
  } else {
    entries.set(key, el);
  }
}

const result = [...entries.values()];

Jonas Wilms
la source
0

Je suis tombé sur cette question en cherchant un moyen de choisir le premier élément d'un tableau qui ne correspond à aucune des valeurs d'un autre tableau et j'ai réussi à le trier éventuellement avec array.find () et array.filter () comme ce

var carList= ['mercedes', 'lamborghini', 'bmw', 'honda', 'chrysler'];
var declinedOptions = ['mercedes', 'lamborghini'];

const nextOption = carList.find(car=>{
    const duplicate = declinedOptions.filter(declined=> {
      return declined === car
    })
    console.log('duplicate:',duplicate) //should list out each declined option
    if(duplicate.length === 0){//if theres no duplicate, thats the nextOption
      return car
    }
})

console.log('nextOption:', nextOption);
//expected outputs
//duplicate: mercedes
//duplicate: lamborghini
//duplicate: []
//nextOption: bmw

si vous devez continuer à chercher une liste mise à jour avant de contre-vérifier la meilleure option suivante, cela devrait fonctionner assez bien :)

Bimpong
la source
-3

Si vous souhaitez utiliser des bibliothèques externes, vous pouvez utiliser _.difference dans le fichier underscore.js pour y parvenir. _.difference renvoie les valeurs du tableau qui ne sont pas présentes dans les autres tableaux.

_.difference([1,2,3,4,5][1,4,10])

==>[2,3,5]
Mubashir Kp
la source
12
Cela ne fonctionne que sur les tableaux avec des valeurs primitives. Si le tableau contient une liste d'objets, comme le demande cette question, il ne fonctionnera pas car il essaie de comparer les références au lieu des objets eux-mêmes, ce qui signifie presque toujours que tout est différent.
Ross Peoples
1
merci Ross tu m'as sauvé d'un mal de tête. J'étais sur le point de signaler un bug pour souligner.
Etienne