Jak uzyskać różnicę między dwiema tablicami obiektów w JavaScript

104

Mam dwa takie zestawy wyników:

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

Ostateczny wynik, jakiego potrzebuję, to różnica między tymi tablicami - ostateczny wynik powinien wyglądać następująco:

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

Czy można zrobić coś takiego w JavaScript?

BKM
źródło
Czy chcesz mieć tablicę wszystkich elementów, które nie występują w obu tablicach, przefiltrowaną według wartości i wyświetlania?
Cerbrus
Chcę różnicę między tymi dwiema tablicami. Wartość będzie obecna w dowolnej tablicy.
BKM
2
Wygląda na to, że tablica jest wyświetlana po zalogowaniu do konsoli firebuga ...
Mike C
2
Przepraszamy, ale obiekt json jest nieprawidłowy ... musisz zmienić = for:
Walter Zalazar

Odpowiedzi:

141

Używając tylko natywnego JS, zadziała coś takiego:

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
źródło
1
To działa i jest być może najlepszą bezpośrednią odpowiedzią, ale byłoby miło przekształcić ją w coś, co zaakceptowało predykat i dwie listy i zwróciło symetryczną różnicę obu list, stosując odpowiednio predykat. (Dodatkowe punkty, gdyby było bardziej wydajne niż dwukrotne
sprawdzanie
@ScottSauyet: Nie znam terminu „predykat” (nie jestem ojczystym językiem angielskim). Czy to jest return a.value ===...w twojej odpowiedzi? (Nawiasem mówiąc, fajne rozwiązanie, +1) Oprócz używania Array.prototype.some(), nie mogę znaleźć bardziej wydajnego / krótszego sposobu na zrobienie tego.
Cerbrus
2
@Cerbrus: Tak. Predykat to funkcja, która zwraca wartość logiczną ( truelub falsewartość). W tym przypadku, jeśli oddzielimy pojęcie testowania równości od reszty kodu, wymagając od użytkownika przejścia sprawdzenia równości jako funkcji, możemy prosty algorytm ogólny.
Scott Sauyet
@ScottSauyet: Zajęło mi trochę czasu,
zanim
ES6 const comparer = (otherArray) => (current) => otherArray.filter ((other) => other.value == current.value && other.display == current.display) .length == 0;
Shnigi
70

Możesz użyć Array.prototype.filter()w połączeniu z Array.prototype.some().

Oto przykład (zakładając, że twoje tablice są przechowywane w zmiennych result1i 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
źródło
43

Dla tych, którzy lubią jednowierszowe rozwiązania w ES6, coś takiego:

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

atean
źródło
3
Wyjaśnij to, proszę!
Bruno Brito
15

Przyjmuję nieco bardziej ogólne podejście, chociaż pomysły są podobne do podejść @Cerbrus i @Kasper Moerch . Tworzę funkcję, która przyjmuje predykat, aby określić, czy dwa obiekty są równe (tutaj ignorujemy tę $$hashKeywłaściwość, ale może to być cokolwiek) i zwracam funkcję, która oblicza symetryczną różnicę dwóch list na podstawie tego predykatu:

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

Ma jedną niewielką przewagę nad podejściem Cerebrusa (podobnie jak podejście Kaspera Moercha), ponieważ ucieka wcześnie; jeśli znajdzie dopasowanie, nie przejmuje się sprawdzaniem reszty listy. Gdybym miał pod curryręką funkcję, zrobiłbym to trochę inaczej, ale to działa dobrze.

Wyjaśnienie

W komentarzu prosiliśmy o bardziej szczegółowe wyjaśnienie dla początkujących. Oto próba.

Przekazujemy następującą funkcję do makeSymmDiffFunc:

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

Ta funkcja pozwala stwierdzić, że dwa obiekty są równe. Podobnie jak wszystkie funkcje, które zwracają wartość truelub false, można ją nazwać „funkcją predykatu”, ale to tylko terminologia. Najważniejsze jest to, że makeSymmDiffFuncjest skonfigurowana z funkcją, która przyjmuje dwa obiekty i zwraca, truejeśli uznamy je za równe, falsejeśli nie.

Używając tego, makeSymmDiffFunc(czytaj "funkcja tworzenia symetrycznej różnicy") zwraca nam nową funkcję:

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

To jest funkcja, której będziemy faktycznie używać. Przekazujemy mu dwie listy i znajduje elementy w pierwszej, a nie w drugiej, a następnie elementy w drugiej nie na pierwszej i łączy te dwie listy.

Jednak patrząc jeszcze raz, zdecydowanie mogłem wziąć wskazówkę z twojego kodu i całkiem uprościć główną funkcję, używając 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));
        };
    };
}());

complementużywa predykatu i zwraca elementy swojej pierwszej listy, a nie drugiej. To jest prostsze niż mój pierwszy przebieg z oddzielną containsfunkcją.

Wreszcie funkcja główna jest opakowana w natychmiast wywołane wyrażenie funkcyjne ( IIFE ), aby utrzymać funkcję wewnętrzną complementpoza zasięgiem globalnym.


Aktualizacja, kilka lat później

Teraz, gdy ES2015 stał się całkiem nieźle wszechobecny, sugerowałbym tę samą technikę, z dużo mniejszą liczbą schematów:

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
źródło
1
Czy mógłbyś dodać więcej wyjaśnień do swojego kodu? Nie jestem pewien, czy początkujący w JavaScript zrozumiałby, jak działa podejście oparte na predykatach.
kaspermoerch,
1
@KasperMoerch: Dodano długie wyjaśnienie. Mam nadzieję że to pomogło. (Pozwoliło mi to również rozpoznać poważny porządek, jaki powinien mieć ten kod.)
Scott Sauyet,
9
import differenceBy from 'lodash/differenceBy'

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

To zwróci różnicę między dwiema tablicami obiektów, używając klucza valuedo ich porównania. Zauważ, że dwie rzeczy o tej samej wartości nie zostaną zwrócone, ponieważ inne klucze są ignorowane.

To jest część lodash .

Noe
źródło
Powyższy obiekt json jest nieprawidłowy. Kiedy spróbujesz w ten sposób, zmień = za:
Walter Zalazar
7

Możesz utworzyć obiekt z kluczami jako unikatową wartością odpowiadającą każdemu obiektowi w tablicy, a następnie filtrować każdą tablicę na podstawie istnienia klucza w obiekcie innej osoby. Zmniejsza złożoność operacji.

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
źródło
5

Myślę, że rozwiązanie @Cerbrus jest na miejscu. Zaimplementowałem to samo rozwiązanie, ale wyodrębniłem powtórzony kod do własnej funkcji (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
źródło
2

Znalazłem to rozwiązanie za pomocą filtra i niektórych.

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

Gorez Tony
źródło
1

Większość odpowiedzi tutaj jest dość złożona, ale czy logika nie stoi za tym całkiem prosta?

  1. sprawdź, która tablica jest dłuższa i podaj ją jako pierwszy parametr (jeśli długość jest równa, kolejność parametrów nie ma znaczenia)
  2. Iteruj po tablicy array1.
  3. Dla bieżącego elementu iteracji tablicy tablica1 sprawdź, czy jest obecny w tablicy2
  4. Jeśli NIE jest obecny, wtedy
  5. Przenieś to do tablicy „różnica”
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) złożoność.

azrahel
źródło
plus 1 za włączenie złożoności
delavago1999
1

możesz zrobić diff a na b i diff b na a, a następnie połączyć oba wyniki

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
źródło
0

Zrobiłem uogólniony diff, który porównuje 2 obiekty dowolnego rodzaju i może uruchomić program obsługi modyfikacji gist.github.com/bortunac "diff.js" , przykład użycia:

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

więc właściwość a jest modyfikowana, b jest usuwana, c modyfikowana, d jest dodawane

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

}

teraz użyj like

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

konsola pokaże

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

Najbardziej ogólny i prosty sposób:

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
źródło
0

Preferuję obiekt mapy, jeśli chodzi o duże tablice.

// 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
źródło
0

JavaScript ma mapy, które zapewniają O (1) czas wstawiania i wyszukiwania. Dlatego można to rozwiązać w O (n) (a nie w O (n²), jak wszystkie inne odpowiedzi). W tym celu konieczne jest wygenerowanie unikalnego klucza pierwotnego (ciąg / numer) dla każdego obiektu. Można JSON.stringify, ale jest to dość podatne na błędy, ponieważ kolejność elementów może wpływać na równość:

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

Dlatego wziąłbym separator, który nie pojawia się w żadnej z wartości i ręcznie skomponowałbym ciąg:

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

Następnie tworzona jest mapa. Gdy element już istnieje na mapie, zostaje usunięty, w przeciwnym razie zostaje dodany. Dlatego pozostają tylko te elementy, które zawierają nieparzyste czasy (czyli tylko raz). To zadziała tylko wtedy, gdy elementy są unikalne w każdej tablicy:

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
źródło
0

Natknąłem się na to pytanie, szukając sposobu na wybranie pierwszego elementu w jednej tablicy, który nie pasuje do żadnej z wartości w innej tablicy, i ostatecznie udało mi się go uporządkować za pomocą array.find () i array.filter (), jak to

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

jeśli chcesz nadal pobierać zaktualizowaną listę przed sprawdzeniem następnej najlepszej opcji, powinno to działać wystarczająco dobrze :)

Bimpong
źródło
-3

Jeśli chcesz korzystać z bibliotek zewnętrznych, możesz użyć _.difference w pliku underscore.js, aby to osiągnąć. _.difference zwraca wartości z tablicy, których nie ma w innych tablicach.

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

==>[2,3,5]
Mubashir Kp
źródło
12
Działa to tylko w przypadku tablic z wartościami pierwotnymi. Jeśli tablica zawiera listę obiektów, jak to zadaje to pytanie, nie zadziała, ponieważ próbuje porównać odniesienia zamiast samych obiektów, co prawie zawsze oznacza, że ​​wszystko jest inne.
Ross Peoples
1
dzięki Ross, uratowałeś mnie od bólu głowy. Miałem właśnie zgłosić błąd do podkreślenia.
Etienne