Как узнать разницу между двумя массивами объектов в JavaScript

104

У меня есть два таких набора результатов:

// 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
Посмотрите здесь: stackoverflow.com/questions/10927722/…
Goran.it
2
Похоже, что массив отображается при входе в консоль firebug ...
Майк Си,
2
Извините, но объект json неправильный ... вам нужно изменить = на:
Уолтер

Ответы:

141

Используя только собственный 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);

Cerbrus
источник
1
Это работает и, возможно, является лучшим прямым ответом, но было бы неплохо превратить его во что-то, что принимает предикат и два списка и возвращает симметричную разницу между двумя списками, применяя предикат соответствующим образом. (Дополнительные баллы, если бы это было более эффективно, чем
повторение
@ScottSauyet: Я не знаком с термином «предикат» (не носитель английского языка). Это return a.value ===...в вашем ответе? (Хорошее решение, кстати, +1) Кроме использования Array.prototype.some(), я не могу найти более эффективный / более короткий способ сделать это.
Cerbrus
2
@Cerbrus: Да. Предикат - это функция, которая возвращает логическое значение ( trueили falseзначение). В этом случае, если мы отделим понятие проверки на равенство от остальной части кода, потребовав от пользователя пройти проверку на равенство как функцию, мы можем сделать простой общий алгоритм.
Скотт Сойет
@ScottSauyet: Мне потребовалось некоторое время, чтобы снова найти этот ответ, но теперь он намного лучше: D
Cerbrus
ES6 const comparer = (otherArray) => (current) => otherArray.filter ((other) => other.value == current.value && other.display == current.display) .length == 0;
Шниги 05
70

Вы можете использовать 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);
каспермёрх
источник
43

Для тех, кто любит однострочные решения в 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: Добавлено длинное объяснение. Надеюсь, это поможет. (Это также заставило меня признать серьезную очистку, которую должен иметь этот код.)
Скотт Сойет
9
import differenceBy from 'lodash/differenceBy'

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

Это вернет разницу между двумя массивами объектов, используя ключ valueдля их сравнения. Обратите внимание, что две вещи с одинаковым значением не будут возвращены, так как другие ключи игнорируются.

Это часть lodash .

Ной
источник
Указанный выше объект json неверен. Когда попробуете этот способ изменить = на:
Вальтер
7

Вы можете создать объект с ключами в качестве уникального значения, соответствующего каждому объекту в массиве, а затем отфильтровать каждый массив на основе наличия ключа в другом объекте. Это снижает сложность операции.

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. Для текущего итерационного элемента array1 проверьте, присутствует ли он в array2
  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

вы можете сделать diff a на b и diff 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

Я сделал обобщенное сравнение, которое сравнивает 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 
Bortunac
источник
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');

yoni12ab
источник
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

если вам нужно продолжать получать обновленный список перед перекрестной проверкой следующего лучшего варианта, это должно работать достаточно хорошо :)

Бимпонг
источник
-3

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

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

==>[2,3,5]
Мубашир КП
источник
12
Это работает только с массивами с примитивными значениями. Если массив содержит список объектов, как в этом вопросе, он не будет работать, поскольку он пытается сравнить ссылки, а не сами объекты, что почти всегда будет означать, что все по-другому.
Ross Peoples
1
Спасибо, Росс, ты спас меня от головной боли. Я собирался сообщить об ошибке, чтобы подчеркнуть.
Этьен