Come ottenere la differenza tra due array di oggetti in JavaScript


101

Ho due set di risultati come questo:

// 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" },
]

Il risultato finale di cui ho bisogno è la differenza tra questi array: il risultato finale dovrebbe essere così:

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

È possibile fare qualcosa di simile in JavaScript?


Quindi, vuoi un array di tutti gli elementi che non si trovano in entrambi gli array, filtrato per valore e visualizzazione?
Cerbrus

Voglio la differenza tra i due array. Il valore sarà presente in uno qualsiasi dell'array.
BKM


2
Sembra che l'array venga visualizzato quando si accede alla console di firebug ...
Mike C

2
Scusa, ma l'oggetto json è sbagliato ... devi cambiare = per:
Walter Zalazar

Risposte:


138

Usando solo JS nativo, qualcosa del genere funzionerà:

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


1
Funziona ed è forse la migliore risposta diretta, ma sarebbe bello trasformarlo in qualcosa che accetti un predicato e due elenchi e restituisca la differenza simmetrica delle due liste applicando il predicato in modo appropriato. (Punti extra se fosse più efficiente che dover eseguire tutti i confronti m * n due volte!)
Scott Sauyet

@ScottSauyet: non ho familiarità con il termine "predicato" (non sono madrelingua inglese). È questo return a.value ===...nella tua risposta? (Bella soluzione, a proposito, +1) A parte l'utilizzo Array.prototype.some(), non riesco davvero a trovare un modo più efficiente / più breve per farlo.
Cerbrus

2
@Cerbrus: Sì. Un predicato è una funzione che restituisce un booleano ( trueo falsevalore). In questo caso, se separiamo la nozione di verifica dell'uguaglianza dal resto del codice richiedendo all'utente di superare il controllo di uguaglianza come funzione, possiamo fare un semplice algoritmo generico.
Scott Sauyet

@ScottSauyet: Mi ci è voluto un po 'per trovare di nuovo questa risposta, ma ora è un po' meglio: D
Cerbrus

ES6 const comparer = (otherArray) => (current) => otherArray.filter ((other) => other.value == current.value && other.display == current.display) .length == 0;
Shnigi

68

Potresti usare Array.prototype.filter()in combinazione con Array.prototype.some().

Ecco un esempio (supponendo che i tuoi array siano memorizzati nelle variabili result1e 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);

40

Per coloro che amano le soluzioni one-liner in ES6, qualcosa del genere:

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


3
Spiegalo per favore!
Bruno Brito

15

Adotto un approccio leggermente più generico, sebbene simile nelle idee agli approcci di @Cerbrus e @Kasper Moerch . Creo una funzione che accetta un predicato per determinare se due oggetti sono uguali (qui ignoriamo la $$hashKeyproprietà, ma potrebbe essere qualsiasi cosa) e restituisco una funzione che calcola la differenza simmetrica di due elenchi in base a quel predicato:

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"}

Ha un piccolo vantaggio rispetto all'approccio di Cerebrus (così come l'approccio di Kasper Moerch) in quanto scappa presto; se trova una corrispondenza, non si preoccupa di controllare il resto della lista. Se avessi una curryfunzione a portata di mano, lo farei in modo leggermente diverso, ma funziona bene.

Spiegazione

Un commento richiedeva una spiegazione più dettagliata per i principianti. Ecco un tentativo.

Passiamo la seguente funzione a makeSymmDiffFunc:

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

Questa funzione è il modo in cui decidiamo che due oggetti sono uguali. Come tutte le funzioni che restituiscono trueo false, può essere chiamata "funzione predicato", ma questa è solo terminologia. Il punto principale è che makeSymmDiffFuncè configurato con una funzione che accetta due oggetti e restituisce truese li consideriamo uguali, falsese non lo facciamo.

Usando quello, makeSymmDiffFunc(leggi "crea funzione differenza simmetrica") ci restituisce una nuova funzione:

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

Questa è la funzione che useremo effettivamente. Gli passiamo due liste e trova gli elementi nella prima non nella seconda, poi quelli nella seconda non nella prima e uniamo queste due liste.

Ripensandoci, però, avrei sicuramente potuto prendere spunto dal tuo codice e semplificare un po 'la funzione principale usando 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));
        };
    };
}());

complementusa il predicato e restituisce gli elementi del suo primo elenco non nel secondo. Questo è più semplice del mio primo passaggio con una containsfunzione separata .

Infine, la funzione principale è racchiusa in un'espressione di funzione richiamata immediatamente ( IIFE ) per mantenere la complementfunzione interna fuori dall'ambito globale.


Aggiornamento, pochi anni dopo

Ora che ES2015 è diventato abbastanza onnipresente, suggerirei la stessa tecnica, con molto meno boilerplate:

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"}

1
Potresti aggiungere qualche spiegazione in più al tuo codice? Non sono sicuro che un principiante in JavaScript capirebbe come funziona l'approccio del predicato.
Kaspermoerch

1
@KasperMoerch: aggiunta una spiegazione piuttosto lunga. Spero che aiuti. (Mi ha anche fatto riconoscere una seria pulizia che questo codice dovrebbe avere.)
Scott Sauyet

8
import differenceBy from 'lodash/differenceBy'

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

Ciò restituirà la differenza tra due array di oggetti, utilizzando la chiave valueper confrontarli. Notare che due cose con lo stesso valore non verranno restituite, poiché le altre chiavi vengono ignorate.

Questa è una parte di lodash .


L'oggetto json sopra è sbagliato. Quando provi in ​​questo modo cambia = per:
Walter Zalazar

6

È possibile creare un oggetto con chiavi come valore univoco corrispondente a ciascun oggetto nell'array e quindi filtrare ogni array in base all'esistenza della chiave nell'oggetto dell'altro. Riduce la complessità dell'operazione.

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


5

Penso che la soluzione @Cerbrus sia perfetta. Ho implementato la stessa soluzione ma ho estratto il codice ripetuto nella sua funzione (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;
  });
}

2

Ho trovato questa soluzione usando il filtro e alcuni.

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


1

La maggior parte delle risposte qui sono piuttosto complesse, ma la logica dietro questo non è abbastanza semplice?

  1. controlla quale array è più lungo e forniscilo come primo parametro (se la lunghezza è uguale, l'ordine dei parametri non ha importanza)
  2. Itera su array1.
  3. Per l'elemento di iterazione corrente di array1 controlla se è presente in array2
  4. Se NON è presente, allora
  5. Spingerlo nella matrice "differenza"
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) complessità.


più 1 per l'inclusione della complessità
delavago1999

1

puoi fare diff a su be diff b su a, quindi unisci entrambi i risultati

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


0

Ho creato un diff generalizzato che confronta 2 oggetti di qualsiasi tipo e posso eseguire un gestore di modifiche gist.github.com/bortunac "diff.js" un ex di utilizzo:

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

quindi la proprietà a viene modificata, b viene eliminata, c modificata, d viene aggiunta

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

}

ora usa come

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

la console mostrerà

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

0

Modo più generico e semplice:

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

0

Preferisco l'oggetto mappa quando si tratta di grandi array.

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


0

JavaScript ha mappe, che forniscono tempo di inserimento e ricerca O (1). Quindi questo può essere risolto in O (n) (e non O (n²) come fanno tutte le altre risposte). Per questo, è necessario generare una chiave primitiva (stringa / numero) univoca per ogni oggetto. Si potrebbe JSON.stringify, ma è abbastanza soggetto a errori in quanto l'ordine degli elementi potrebbe influenzare l'uguaglianza:

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

Pertanto, prenderei un delimitatore che non appare in nessuno dei valori e compongo manualmente una stringa:

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

Quindi viene creata una mappa. Quando un elemento esiste già nella mappa, viene rimosso, altrimenti viene aggiunto. Pertanto rimangono solo gli elementi che sono inclusi tempi dispari (ovvero una sola volta). Funzionerà solo se gli elementi sono univoci in ogni matrice:

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


0

Mi sono imbattuto in questa domanda mentre cercavo un modo per scegliere il primo elemento in un array che non corrisponde a nessuno dei valori in un altro array e sono riuscito a risolverlo alla fine con array.find () e array.filter () come Questo

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

se è necessario continuare a recuperare un elenco aggiornato prima del controllo incrociato per la migliore opzione successiva, questo dovrebbe funzionare abbastanza bene :)


-2

Se desideri utilizzare librerie esterne, puoi utilizzare _.difference in underscore.js per ottenere ciò. _.difference restituisce i valori dell'array che non sono presenti negli altri array.

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

==>[2,3,5]

11
Funziona solo su array con valori primitivi. Se l'array contiene un elenco di oggetti, come questa domanda chiede, non funzionerà poiché cerca di confrontare i riferimenti invece degli oggetti stessi, il che significa quasi sempre che tutto è diverso.
Ross Peoples

1
grazie Ross mi hai salvato da un mal di testa. Stavo per segnalare un bug da sottolineare.
Etienne
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.