So ermitteln Sie den Unterschied zwischen zwei Arrays von Objekten in JavaScript

100

Ich habe zwei Ergebnismengen wie diese:

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

Das Endergebnis, das ich brauche, ist der Unterschied zwischen diesen Arrays - das Endergebnis sollte folgendermaßen aussehen:

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

Ist es möglich, so etwas in JavaScript zu tun?

BKM
quelle
Sie möchten also ein Array aller Elemente, die nicht in beiden Arrays vorkommen, gefiltert nach Wert und Anzeige?
Cerbrus
Ich möchte den Unterschied zwischen den beiden Arrays. Der Wert ist in einem beliebigen Array vorhanden.
BKM
Schauen Sie hier: stackoverflow.com/questions/10927722/…
Goran.it
2
Es sieht aus wie das Array, wie es angezeigt wird, wenn es an der Firebug-Konsole angemeldet ist ...
Mike C
2
Sorry, aber das JSON-Objekt ist falsch ... Sie müssen ändern = für:
Walter Zalazar

Antworten:

136

Wenn Sie nur natives JS verwenden, funktioniert so etwas:

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
quelle
1
Dies funktioniert und ist vielleicht die beste direkte Antwort, aber es wäre schön, daraus etwas zu machen, das ein Prädikat und zwei Listen akzeptiert und die symmetrische Differenz der beiden Listen durch entsprechende Anwendung des Prädikats zurückgibt. (Extra Punkte, wenn es effizienter wäre, als alle m * n Vergleiche zweimal
durchlaufen zu müssen
@ScottSauyet: Ich kenne den Begriff "Prädikat" nicht (kein englischer Muttersprachler). Ist das das return a.value ===...in deiner Antwort? (Gute Lösung übrigens, +1) Abgesehen von der Verwendung Array.prototype.some()kann ich keinen effizienteren / kürzeren Weg finden, dies zu tun.
Cerbrus
2
@ Cerbrus: Ja. Ein Prädikat ist eine Funktion, die einen Booleschen Wert ( trueoder falseWert) zurückgibt . In diesem Fall können wir den Begriff des Testens auf Gleichheit vom Rest des Codes trennen, indem der Benutzer die Gleichheitsprüfung als Funktion bestehen muss ein einfacher generischer Algorithmus.
Scott Sauyet
@ ScottSauyet: Es hat eine Weile gedauert, bis ich diese Antwort wieder gefunden habe, aber jetzt ist es ein bisschen besser: D
Cerbrus
ES6 const compareer = (otherArray) => (current) => otherArray.filter ((other) => other.value == current.value && other.display == current.display) .length == 0;
Shnigi
68

Sie könnten Array.prototype.filter()in Kombination mit verwenden Array.prototype.some().

Hier ist ein Beispiel (vorausgesetzt, Ihre Arrays sind in den Variablen result1und gespeichert 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
quelle
40

Für diejenigen, die Einzeilerlösungen in ES6 mögen, so etwas:

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
quelle
3
Erklären Sie es bitte!
Bruno Brito
15

Ich verfolge einen etwas allgemeineren Ansatz, obwohl die Ideen den Ansätzen von @Cerbrus und @Kasper Moerch ähnlich sind . Ich erstelle eine Funktion, die ein Prädikat akzeptiert, um festzustellen, ob zwei Objekte gleich sind (hier ignorieren wir die $$hashKeyEigenschaft, aber es kann alles sein) und gebe eine Funktion zurück, die die symmetrische Differenz zweier Listen basierend auf diesem Prädikat berechnet:

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

Es hat einen kleinen Vorteil gegenüber Cerebrus 'Ansatz (wie auch Kasper Moerchs Ansatz), da es früh entkommt; Wenn eine Übereinstimmung gefunden wird, wird der Rest der Liste nicht überprüft. Wenn ich eine curryFunktion zur Hand hätte, würde ich das etwas anders machen, aber das funktioniert gut.

Erläuterung

In einem Kommentar wurde um eine ausführlichere Erklärung für Anfänger gebeten. Hier ist ein Versuch.

Wir übergeben die folgende Funktion an makeSymmDiffFunc:

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

Mit dieser Funktion entscheiden wir, dass zwei Objekte gleich sind. Wie alle Funktionen, die trueoder zurückgeben false, kann es als "Prädikatfunktion" bezeichnet werden, aber das ist nur Terminologie. Der Hauptpunkt ist, dass makeSymmDiffFuncmit einer Funktion konfiguriert wird, die zwei Objekte akzeptiert und zurückgibt, truewenn wir sie als gleich betrachten, falsewenn wir dies nicht tun.

Wenn Sie dies verwenden makeSymmDiffFunc(lesen Sie "Symmetrische Differenzfunktion machen"), erhalten Sie eine neue Funktion:

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

Dies ist die Funktion, die wir tatsächlich verwenden werden. Wir übergeben ihm zwei Listen und es findet die Elemente in der ersten nicht in der zweiten, dann die in der zweiten nicht in der ersten und kombinieren diese beiden Listen.

Wenn ich noch einmal darüber nachdenke, hätte ich definitiv einen Hinweis auf Ihren Code nehmen und die Hauptfunktion ein wenig vereinfachen können, indem ich Folgendes verwendet habe 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));
        };
    };
}());

complementverwendet das Prädikat und gibt die Elemente der ersten Liste zurück, die nicht in der zweiten Liste enthalten sind. Dies ist einfacher als mein erster Durchgang mit einer separaten containsFunktion.

Schließlich wird die Hauptfunktion in einen sofort aufgerufenen Funktionsausdruck ( IIFE ) eingeschlossen, um die interne complementFunktion aus dem globalen Bereich herauszuhalten.


Update einige Jahre später

Jetzt, da ES2015 ziemlich allgegenwärtig geworden ist, würde ich die gleiche Technik mit viel weniger Boilerplate vorschlagen:

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
quelle
1
Könnten Sie Ihrem Code weitere Erklärungen hinzufügen? Ich bin nicht sicher, ob ein Anfänger in JavaScript verstehen würde, wie der Prädikat-Ansatz funktioniert.
Kaspermoerch
1
@ KasperMoerch: Eine längere Erklärung hinzugefügt. Ich hoffe das hilft. (Es hat mich auch dazu gebracht, eine ernsthafte Bereinigung zu erkennen, die dieser Code haben sollte.)
Scott Sauyet
8
import differenceBy from 'lodash/differenceBy'

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

Dadurch wird die Differenz zwischen zwei Arrays von Objekten zurückgegeben, wobei der Schlüssel valuezum Vergleichen verwendet wird. Beachten Sie, dass zwei Dinge mit demselben Wert nicht zurückgegeben werden, da die anderen Schlüssel ignoriert werden.

Dies ist ein Teil von lodash .

Noah
quelle
Das obige json-Objekt ist falsch. Wenn Sie dies versuchen, ändern Sie = für:
Walter Zalazar
6

Sie können ein Objekt mit Schlüsseln als eindeutigen Wert für jedes Objekt im Array erstellen und dann jedes Array basierend auf dem Vorhandensein des Schlüssels im Objekt des anderen filtern. Dies reduziert die Komplexität der Operation.

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
quelle
5

Ich denke, die @ Cerbrus-Lösung ist genau richtig. Ich habe dieselbe Lösung implementiert, aber den wiederholten Code in seine eigene Funktion (DRY) extrahiert.

 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
quelle
2

Ich habe diese Lösung mit Filter und einigen gefunden.

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

Gorez Tony
quelle
1

Die meisten Antworten hier sind ziemlich komplex, aber ist die Logik dahinter nicht ganz einfach?

  1. Überprüfen Sie, welches Array länger ist, und geben Sie es als ersten Parameter an (wenn die Länge gleich ist, spielt die Reihenfolge der Parameter keine Rolle).
  2. Iterieren Sie über Array1.
  3. Überprüfen Sie für das aktuelle Iterationselement von Array1, ob es in Array2 vorhanden ist
  4. Wenn es NICHT vorhanden ist, als
  5. Schieben Sie es auf "Differenz" -Array
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) Komplexität.

Azrahel
quelle
plus 1 für die Einbeziehung von Komplexität
delavago1999
1

Sie können diff a auf b und diff b auf a ausführen und dann beide Ergebnisse zusammenführen

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
quelle
0

Ich habe eine generali diff gemacht , die zwei Objekte jeglicher Art zu vergleichen , und kann eine Modifikation - Handler gist.github.com/bortunac „diff.js“ eine Ex verwenden:

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

also wird die Eigenschaft a geändert, b wird gelöscht, c wird geändert, d wird hinzugefügt

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

}}

Verwenden Sie jetzt wie

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

Die Konsole wird angezeigt

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

Der allgemeinste und einfachste Weg:

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
quelle
0

Ich bevorzuge Kartenobjekte, wenn es um große Arrays geht.

// 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
quelle
0

JavaScript verfügt über Karten, die eine Einfüge- und Suchzeit von O (1) bieten. Daher kann dies in O (n) gelöst werden (und nicht in O (n²) wie alle anderen Antworten). Dazu muss für jedes Objekt ein eindeutiger Grundschlüssel (String / Nummer) generiert werden. Man könnte JSON.stringify, aber das ist ziemlich fehleranfällig, da die Reihenfolge der Elemente die Gleichheit beeinflussen könnte:

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

Daher würde ich ein Trennzeichen nehmen, das in keinem der Werte vorkommt, und eine Zeichenfolge manuell erstellen:

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

Dann wird eine Karte erstellt. Wenn ein Element bereits in der Karte vorhanden ist, wird es entfernt, andernfalls wird es hinzugefügt. Daher bleiben nur die Elemente übrig, die ungerade Zeiten enthalten (dh nur einmal). Dies funktioniert nur, wenn die Elemente in jedem Array eindeutig sind:

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
quelle
0

Ich bin auf diese Frage gestoßen, als ich nach einer Möglichkeit gesucht habe, das erste Element in einem Array auszuwählen, das keinem der Werte in einem anderen Array entspricht, und habe es schließlich geschafft, es mit array.find () und array.filter () wie zu sortieren Dies

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

Wenn Sie eine aktualisierte Liste abrufen müssen, bevor Sie nach der nächstbesten Option suchen, sollte dies gut genug funktionieren :)

Bimpong
quelle
-2

Wenn Sie bereit sind, externe Bibliotheken zu verwenden, können Sie _.difference in underscore.js verwenden, um dies zu erreichen. _.difference gibt die Werte aus dem Array zurück, die in den anderen Arrays nicht vorhanden sind.

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

==>[2,3,5]
Mubashir Kp
quelle
11
Dies funktioniert nur bei Arrays mit primitiven Werten. Wenn das Array eine Liste von Objekten enthält, wie in dieser Frage beschrieben, funktioniert es nicht, da versucht wird, die Referenzen anstelle der Objekte selbst zu vergleichen, was fast immer bedeutet, dass alles anders ist.
Ross Peoples
1
Danke Ross, du hast mich vor Kopfschmerzen gerettet. Ich wollte gerade einen Fehler melden, um ihn zu unterstreichen.
Etienne