JavaScriptでオブジェクトの2つの配列の違いを取得する方法


100

次のような2つの結果セットがあります。

// 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でこのようなことをすることは可能ですか?


それで、値と表示でフィルタリングされた、両方の配列に存在しないすべての要素の配列が必要ですか?
セルブルス2014

2つの配列の違いが欲しい。値は配列のいずれかに存在します。
BKM 2014


2
Firebugコンソールにログインしたときに表示される配列のように見えます
Mike C

2
申し訳ありませんが、jsonオブジェクトが間違っています... =を変更する必要があります:
Walter Zalazar 2017

回答:


136

ネイティブ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
これは機能し、おそらく最良の直接的な答えですが、述語と2つのリストを受け入れ、述語を適切に適用することによって2つのリストの対称的な差を返すものに変えるとよいでしょう。(m * nのすべての比較を2回実行するよりも効率的だった場合の追加ポイント!)
Scott Sauyet 14

@ScottSauyet:私は「述語」という言葉に精通していません(英語のネイティブスピーカーではありません)。それはreturn a.value ===...あなたの答えですか?(ちなみに、+ 1の方法)を使用する以外に、Array.prototype.some()これを行うためのより効率的で短い方法を実際に見つけることはできません。
セルブルス

2
@セルブルス:はい。述語はブール値(trueまたはfalse値)を返す関数です。この場合、ユーザーに同等性チェックを関数として渡すことを要求することにより、同等性のテストの概念を残りのコードから切り離すと、次のようになります。簡単な汎用アルゴリズム。
Scott Sauyet、2014

@ScottSauyet:この回答をもう一度見つけるのには少し
時間がかかり

ES6 const comparer =(otherArray)=>(current)=> otherArray.filter((other)=> other.value == current.value && other.display == current.display).length == 0;
Shnigi

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
説明してください!
Bruno Brito

15

@Cerbrus@Kasper Moerchの両方のアプローチとアイデアは似ていますが、私はもう少し汎用的なアプローチを採用しています。述語を受け入れて2つのオブジェクトが等しいかどうかを判断する関数を作成し(ここでは$$hashKeyプロパティを無視しますが、何でもかまい ません)、その述語に基づいて2つのリストの対称差を計算する関数を返します。

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

これは、早期にエスケープするという点で、セレブルスのアプローチ(Kasper Moerchのアプローチと同様)に比べて1つの小さな利点があります。一致が見つかった場合、リストの残りをチェックする必要はありません。curry便利な機能がある場合、これは少し違った方法で行いますが、これは問題なく機能します。

説明

コメントは初心者のためのより詳細な説明を求めました。これが試みです。

次の関数をに渡しますmakeSymmDiffFunc

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

この関数は、2つのオブジェクトが等しいと判断する方法です。trueまたはを返すすべての関数と同様にfalse、「述語関数」と呼ぶことができますが、それは単なる用語です。主なポイントは、makeSymmDiffFunc2つのオブジェクトを受け入れtrue、それらが等しいと見なす場合は等しくない場合に戻る関数で構成されていることfalseです。

これを使用するとmakeSymmDiffFunc(「対称差関数を作成」を参照)、新しい関数が返されます。

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

これが実際に使用する関数です。2つのリストを渡して、最初の要素が2番目の要素ではなく、2番目の要素が最初の要素ではない要素を見つけ、これら2つのリストを結合します。

ただし、もう一度見てみると、間違いなくコードからヒントを得て、次のコマンドを使用することでmain関数をかなり簡略化できます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述語を使用し、2番目ではなく最初のリストの要素を返します。これは、個別の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 14

1
@KasperMoerch:長めの説明を追加。お役に立てば幸いです。(また、このコードが持つべき重大なクリーンアップを認識させられました。)
Scott Sauyet 14

8
import differenceBy from 'lodash/differenceBy'

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

これは、キーを使用して、オブジェクトの2つの配列の違いを返します valueをを比較します。他のキーは無視されるため、同じ値を持つ2つのものは返されないことに注意してください。

これはlodashの一部です。


上記の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. array1を反復処理します。
  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

bでaを比較し、aでbを比較してから、両方の結果をマージすることができます

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"を実行できる一般化されたdiffを作成しました 。

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;

次に、マップが作成されます。要素がすでにMapに存在する場合は削除され、存在しない場合は追加されます。したがって、奇数回(つまり、1回だけ)含まれる要素のみが残ります。これは、要素が各配列で一意である場合にのみ機能します。

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

別の配列の値のいずれにも一致しない1つの配列の最初の項目を選択する方法を探しているときにこの質問に出くわし、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

外部ライブラリを使用する場合は、underscore.jsで_.differenceを使用してこれを実現できます。_.differenceは、他の配列には存在しない配列の値を返します。

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

==>[2,3,5]

11
これは、プリミティブ値を持つ配列でのみ機能します。この質問が尋ねるように、配列にオブジェクトのリストが含まれている場合、オブジェクト自体ではなく参照を比較しようとするため、配列は機能しません。これは、ほとんど常にすべてが異なることを意味します。
ロスピープルズ

1
おかげでロスは私を頭痛から救った。アンダースコアにバグを報告しようとしていました。
エティエンヌ2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.