Як отримати різницю між двома масивами об’єктів у JavaScript


101

У мене є два набори результатів, як це:

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

Кінцевий результат, який мені потрібен, - це різниця між цими масивами - кінцевий результат повинен бути таким:

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

Чи можна зробити щось подібне в JavaScript?


Отже, вам потрібен масив усіх елементів, які не зустрічаються в обох масивах, відфільтрований за значенням і відображенням?
Cerbrus

Я хочу різницю між двома масивами. Значення буде присутнє в будь-якому з масиву.
BKM


2
Він схожий на масив, який відображається під час входу в консоль firebug ...
Майк C

2
Вибачте, але об’єкт json невірний ... вам потрібно змінити = для:
Вальтер Залазар

Відповіді:


139

Використовуючи лише рідний JS, щось подібне буде працювати:

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
Це працює, і, можливо, найкраща пряма відповідь, але було б непогано перетворити його на щось, що прийняло предикат і два списки та повернуло симетричну різницю двох списків, застосувавши предикат належним чином. (Додаткові бали, якби це було ефективніше, ніж двічі проходити всі порівняння m * n!)
Скотт Сауєт

@ScottSauyet: Я не знайомий з терміном "предикат" (не є носієм англійської мови). Це return a.value ===...у вашій відповіді? (Приємне рішення, до речі, +1) Окрім використання Array.prototype.some(), я насправді не можу знайти більш ефективний / коротший спосіб зробити це.
Cerbrus

2
@Cerbrus: Так. Присудок - це функція, яка повертає булеве значення ( trueабо falseзначення.) У цьому випадку, якщо ми відокремимо поняття тестування на рівність від решти коду, вимагаючи від користувача пройти перевірку рівності як функції, ми можемо зробити прямий загальний алгоритм.
Scott Sauyet

@ScottSauyet: Минув час, щоб знову знайти цю відповідь, але зараз це вже трохи краще: D
Cerbrus

ES6 const comparer = (otherArray) => (поточний) => otherArray.filter ((інший) => other.value == current.value && other.display == current.display) .length == 0;
Шнігі

68

Ви можете використовувати Array.prototype.filter()в поєднанні з Array.prototype.some().

Ось приклад (припускаючи, що ваші масиви зберігаються у змінних result1і 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

Для тих, хто любить однолінійні рішення в ES6, щось подібне:

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
Поясніть це будь ласка!
Бруно Бріто,

15

Я приймаю дещо більш загальний підхід, хоча подібний за ідеями до підходів як @Cerbrus, так і @Kasper Moerch . Я створюю функцію, яка приймає предикат, щоб визначити, чи два об'єкти рівні (тут ми ігноруємо $$hashKeyвластивість, але це може бути що завгодно) і повертаю функцію, яка обчислює симетричну різницю двох списків на основі цього предиката:

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

Він має одну незначну перевагу над підходом Церебруса (як і підхід Каспера Моерха) тим, що він втікає рано; якщо він знаходить відповідність, це не заважає перевірити решту списку. Якби у мене була curryзручна функція, я б робив це дещо інакше, але це працює чудово.

Пояснення

У коментарі просили більш детального пояснення для початківців. Ось спроба.

Ми передаємо таку функцію makeSymmDiffFunc:

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

Ця функція полягає в тому, як ми вирішуємо, що два об'єкти рівні. Як і всі функції, які повертають trueабо false, її можна назвати "предикатною функцією", але це лише термінологія. Головне, це те, що makeSymmDiffFuncналаштовано на функцію, яка приймає два об’єкти і повертається, trueякщо ми вважаємо їх рівними, falseякщо ні.

Використовуючи це, makeSymmDiffFunc(читайте "зробити функцію симетричної різниці") повертає нам нову функцію:

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

Це функція, яку ми будемо реально використовувати. Ми передаємо йому два списки, і він знаходить елементи в першому, а не в другому, потім ті, що знаходяться у другому, не в першому, і поєднує ці два списки.

Однак, переглядаючи це ще раз, я точно міг взяти підказку з вашого коду і трохи спростити основну функцію, використовуючи 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використовує предикат і повертає елементи свого першого списку, а не другого. Це простіше, ніж мій перший прохід з окремою containsфункцією.

Нарешті, основна функція обертається виразом функції, що викликається одразу ( IIFE ), щоб внутрішня complementфункція не виходила за межі глобальної області.


Оновлення через кілька років

Тепер, коли ES2015 став досить повсюдним, я б запропонував ту саму техніку, з набагато меншим типовим шаблоном:

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
Не могли б ви додати ще якесь пояснення до свого коду? Я не впевнений, що початківець у JavaScript зрозуміє, як працює предикатний підхід.
kaspermoerch

1
@KasperMoerch: Додано тривале пояснення. Я сподіваюся, що це допомагає. (Це також змусило мене визнати серйозну процедуру очищення, яку повинен мати цей код.)
Скотт Сойет,

8
import differenceBy from 'lodash/differenceBy'

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

Це поверне різницю між двома масивами об'єктів, використовуючи ключ valueдля їх порівняння. Зауважте, дві речі з однаковим значенням не повертаються, оскільки інші клавіші ігноруються.

Це частина лодашу .


Об'єкт json вище неправильний. При спробі таким чином змінити = для:
Вальтер Залазар

6

Ви можете створити об'єкт з ключами як унікальне значення, що відповідає кожному об'єкту в масиві, а потім відфільтрувати кожен масив на основі існування ключа в об'єкті іншого. Це знижує трудомісткість операції.

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

Я думаю, що рішення @Cerbrus не зафіксовано. Я реалізував те саме рішення, але витягнув повторюваний код у власну функцію (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

Я знайшов це рішення за допомогою фільтра та деяких.

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


1

Більшість відповідей тут досить складні, але чи не логіка цього досить проста?

  1. перевірте, який масив довший і введіть його як перший параметр (якщо довжина дорівнює, параметр порядку не має значення)
  2. Ітерація над масивом1.
  3. Для поточного ітераційного елемента масиву1 перевірте, чи він присутній у масиві2
  4. Якщо його немає, ніж
  5. Натисніть його на масив "різниця"
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) складність.


плюс 1 для включення складності
delavago1999

1

ви можете зробити різницю a на b і різницю b на a, а потім об’єднати обидва результати

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

Я створив узагальнений diff, який порівнює 2 об'єкти будь-якого типу і може запускати обробник модифікацій gist.github.com/bortunac "diff.js", коли використовується:

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

тому властивість a модифікується, b видаляється, c модифікується, d додається

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

}

тепер використовуйте як

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

покаже консоль

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

0

Найбільш загальний і простий спосіб:

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

Я віддаю перевагу об'єкту карти, коли мова йде про великі масиви.

// 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 є Карти, які забезпечують час вставки та пошуку O (1). Тому це можна вирішити в O (n) (а не O (n²), як це роблять усі інші відповіді). Для цього необхідно створити унікальний примітивний (рядок / число) ключ для кожного об'єкта. Можна JSON.stringify, але це досить схильна помилка, оскільки порядок елементів може впливати на рівність:

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

Тому я б взяв роздільник, який не відображається в жодному зі значень, і скласти рядок вручну:

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

Потім створюється Карта. Коли елемент вже є на карті, він видаляється, інакше він додається. Тому залишаються лише елементи, включені в непарні часи (маючи на увазі лише один раз). Це буде працювати лише в тому випадку, якщо елементи унікальні у кожному масиві:

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

Я зіткнувся з цим питанням під час пошуку способу виділити перший елемент в одному масиві, який не відповідає жодному зі значень іншого масиву, і вдалося впорядкувати його зрештою за допомогою array.find () та array.filter (), як це

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

якщо вам потрібно продовжувати отримувати оновлений список, перш ніж перехресно перевірити наступний найкращий варіант, це має працювати досить добре :)


-2

Якщо ви бажаєте використовувати зовнішні бібліотеки, ви можете використовувати _.difference в underscore.js, щоб досягти цього. _.difference повертає значення з масиву, яких немає в інших масивах.

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

==>[2,3,5]

11
Це працює лише на масивах з примітивними значеннями. Якщо масив містить перелік об'єктів, як задається цим питанням, він не працюватиме, оскільки він намагається порівняти посилання замість самих об’єктів, що майже завжди означатиме, що все по-іншому.
Росс

1
дякую Россу, що ти врятував мене від головного болю. Я збирався повідомити про помилку підкреслення.
Етьєн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.