Cara mendapatkan perbedaan antara dua array objek di JavaScript

101

Saya memiliki dua set hasil seperti ini:

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

Hasil akhir yang saya butuhkan adalah perbedaan antara array ini - hasil akhirnya harus seperti ini:

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

Apakah mungkin melakukan sesuatu seperti ini di JavaScript?

BKM
sumber
Jadi, Anda menginginkan array dari semua elemen yang tidak muncul di kedua array, difilter berdasarkan nilai & tampilan?
Cerbrus
Saya ingin perbedaan antara dua larik. Nilainya akan ada di salah satu larik.
BKM
2
Sepertinya array seperti yang ditampilkan saat login ke konsol firebug ...
Mike C
2
Maaf, tapi objek json salah ... Anda perlu mengubah = untuk:
Walter Zalazar

Jawaban:

138

Hanya menggunakan JS asli, sesuatu seperti ini akan berfungsi:

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
sumber
1
Ini berfungsi, dan mungkin merupakan jawaban langsung terbaik, tetapi alangkah baiknya mengubahnya menjadi sesuatu yang menerima predikat dan dua daftar dan mengembalikan perbedaan simetris dari kedua daftar dengan menerapkan predikat secara tepat. (Poin ekstra jika lebih efisien daripada harus melalui semua perbandingan dua kali!)
Scott Sauyet
@ScottSauyet: Saya tidak akrab dengan istilah "predikat" (Bukan penutur asli bahasa Inggris). Apakah itu return a.value ===...jawaban Anda? (Solusi bagus, omong-omong, +1) Selain menggunakan Array.prototype.some(), saya tidak dapat menemukan cara yang lebih efisien / lebih singkat untuk melakukan ini.
Cerbrus
2
@ Cerbrus: Ya. Predikat adalah fungsi yang mengembalikan boolean ( trueatau falsenilai.) Dalam hal ini, jika kita memisahkan gagasan pengujian persamaan dari kode lainnya dengan meminta pengguna untuk melewati pemeriksaan kesetaraan sebagai fungsi, kita dapat membuat algoritme umum yang langsung.
Scott Sauyet
@ScottSauyet: Butuh beberapa saat bagi saya untuk menemukan jawaban ini lagi, tapi sekarang sudah sedikit lebih baik: D
Cerbrus
ES6 const comparer = (otherArray) => (current) => otherArray.filter ((other) => other.value == current.value && other.display == current.display) .length == 0;
Shnigi
68

Anda bisa menggunakan Array.prototype.filter()kombinasi dengan Array.prototype.some().

Berikut adalah contohnya (dengan asumsi array Anda disimpan dalam variabel result1dan 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
sumber
40

Bagi mereka yang menyukai solusi satu baris di ES6, seperti ini:

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
sumber
3
Tolong jelaskan itu!
Bruno Brito
15

Saya mengambil pendekatan dengan tujuan yang sedikit lebih umum, meskipun memiliki kesamaan ide dengan pendekatan @Cerbrus dan @Kasper Moerch . Saya membuat fungsi yang menerima predikat untuk menentukan apakah dua objek sama (di sini kita mengabaikan $$hashKeyproperti, tetapi bisa jadi apa saja) dan mengembalikan fungsi yang menghitung perbedaan simetris dari dua daftar berdasarkan predikat itu:

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

Ia memiliki satu keuntungan kecil dibandingkan pendekatan Cerebrus (seperti halnya pendekatan Kasper Moerch) karena ia lolos lebih awal; jika menemukan kecocokan, tidak perlu repot memeriksa daftar lainnya. Jika saya punyacurry fungsi yang berguna, saya akan melakukan ini sedikit berbeda, tetapi ini berfungsi dengan baik.

Penjelasan

Sebuah komentar meminta penjelasan yang lebih detail untuk pemula. Ini usahanya.

Kami meneruskan fungsi berikut ke makeSymmDiffFunc:

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

Fungsi ini adalah bagaimana kita memutuskan bahwa dua objek itu sama. Seperti semua fungsi yang mengembalikan trueatau false, itu bisa disebut "fungsi predikat", tapi itu hanya terminologi. Poin utamanya adalah yang makeSymmDiffFuncdikonfigurasi dengan fungsi yang menerima dua objek dan mengembalikan truejika kita menganggapnya sama, falsejika tidak.

Menggunakan itu, makeSymmDiffFunc(baca "membuat perbedaan fungsi simetris") mengembalikan kita fungsi baru:

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

Ini adalah fungsi yang akan kita gunakan. Kami memberikan dua daftar dan menemukan elemen di yang pertama bukan yang kedua, lalu yang kedua bukan yang pertama dan menggabungkan kedua daftar ini.

Namun, memeriksanya lagi, saya pasti bisa mengambil isyarat dari kode Anda dan menyederhanakan fungsi utama sedikit dengan menggunakan 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));
        };
    };
}());

complementmenggunakan predikat dan mengembalikan elemen dari daftar pertamanya, bukan yang kedua. Ini lebih sederhana daripada pass pertama saya dengan containsfungsi terpisah .

Terakhir, fungsi utama dibungkus dalam ekspresi fungsi yang dipanggil segera ( IIFE ) untuk menjaga complementfungsi internal tetap berada di luar cakupan global.


Perbarui, beberapa tahun kemudian

Sekarang ES2015 telah menjadi cukup baik di mana-mana, saya akan menyarankan teknik yang sama, dengan boilerplate yang jauh lebih sedikit:

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
sumber
1
Bisakah Anda menambahkan lebih banyak penjelasan ke kode Anda? Saya tidak yakin seorang pemula dalam JavaScript akan memahami cara kerja pendekatan predikat.
kaspermoerch
1
@KasMoerch: Menambahkan penjelasan gondrong. Saya harap itu membantu. (Itu juga membuat saya mengenali pembersihan serius yang seharusnya dilakukan oleh kode ini.)
Scott Sauyet
8
import differenceBy from 'lodash/differenceBy'

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

Ini akan mengembalikan perbedaan antara dua array objek, menggunakan kunci value untuk membandingkannya. Perhatikan dua hal dengan nilai yang sama tidak akan dikembalikan, karena kunci lainnya diabaikan.

Ini adalah bagian dari lodash .

Nuh
sumber
Objek json di atas salah. Ketika mencoba cara ini ubah = untuk:
Walter Zalazar
6

Anda dapat membuat objek dengan kunci sebagai nilai unik yang sesuai untuk setiap objek dalam array dan kemudian memfilter setiap array berdasarkan keberadaan kunci dalam objek lain. Ini mengurangi kompleksitas operasi.

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

Saya pikir solusi @Cerbrus sudah tepat. Saya telah menerapkan solusi yang sama tetapi mengekstrak kode berulang ke dalam fungsinya sendiri (KERING).

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

Saya menemukan solusi ini menggunakan filter dan beberapa.

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

Gorez Tony
sumber
1

Sebagian besar jawaban di sini agak rumit, tetapi bukankah logika di balik ini cukup sederhana?

  1. periksa larik mana yang lebih panjang dan sediakan sebagai parameter pertama (jika panjangnya sama, urutan parameter tidak menjadi masalah)
  2. Iterasi di atas array1.
  3. Untuk elemen iterasi saat ini dari array1, periksa apakah elemen itu ada dalam array2
  4. Jika TIDAK ada, maka
  5. Dorong ke array 'perbedaan'
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;
}

Kompleksitas O (n ^ 2).

azrahel
sumber
ditambah 1 untuk inklusi kompleksitas
delavago1999
1

Anda dapat melakukan diff a pada b dan diff b pada a, kemudian menggabungkan kedua hasil tersebut

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.dll
sumber
0

Saya telah membuat perbedaan umum yang membandingkan 2 objek dalam bentuk apa pun dan dapat menjalankan penangan modifikasi gist.github.com/bortunac "diff.js" sebagai contoh penggunaan:

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

jadi properti a diubah, b dihapus, c diubah, d ditambahkan

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

}

sekarang gunakan seperti

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

konsol akan ditampilkan

mdf ["a"]  1 | 2 
mdf ["c", "1"]  2 | 3 
add ["c", "2"]  undefined | 5 
add ["d"]  undefined | 55 
del ["b"]  2 | undefined 
bortunac.dll
sumber
0

Cara paling umum dan sederhana:

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

Saya lebih suka objek peta jika menyangkut array besar.

// 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.dll
sumber
0

JavaScript memiliki Maps, yang menyediakan waktu penyisipan dan pencarian O (1). Oleh karena itu, ini dapat diselesaikan dalam O (n) (dan bukan O (n²) seperti semua jawaban lainnya). Untuk itu, perlu dibuat kunci primitif unik (string / angka) untuk setiap objek. Bisa saja JSON.stringify, tetapi itu cukup rawan kesalahan karena urutan elemen dapat memengaruhi kesetaraan:

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

Oleh karena itu, saya akan mengambil pembatas yang tidak muncul di salah satu nilai dan menulis string secara manual:

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

Kemudian Peta dibuat. Ketika sebuah elemen sudah ada di Peta, itu akan dihapus, jika tidak maka akan ditambahkan. Oleh karena itu hanya elemen yang termasuk waktu ganjil (artinya hanya sekali) yang tersisa. Ini hanya akan berfungsi jika elemennya unik di setiap larik:

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

Saya menemukan pertanyaan ini saat mencari cara untuk memilih item pertama dalam satu array yang tidak cocok dengan salah satu nilai dalam array lain dan berhasil menyelesaikannya pada akhirnya dengan array.find () dan array.filter () like ini

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

jika Anda perlu terus mengambil daftar yang diperbarui sebelum memeriksa ulang untuk opsi terbaik berikutnya, ini akan bekerja dengan cukup baik :)

Bimpong
sumber
-2

Jika Anda ingin menggunakan pustaka eksternal, Anda dapat menggunakan _.difference di underscore.js untuk melakukannya. _.difference mengembalikan nilai dari array yang tidak ada di array lain.

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

==>[2,3,5]
Mubashir Kp
sumber
11
Ini hanya berfungsi pada array dengan nilai primitif. Jika array berisi daftar objek, seperti yang ditanyakan oleh pertanyaan ini, ini tidak akan berfungsi saat mencoba membandingkan referensi alih-alih objek itu sendiri, yang hampir selalu berarti bahwa semuanya berbeda.
Ross Peoples
1
terima kasih Ross, kau menyelamatkanku dari sakit kepala. Saya akan melaporkan bug ke garis bawah.
Etienne