web-dev-qa-db-ja.com

2つのオブジェクトのキーの共通部分を取得する最良の方法は?

次のような2つのオブジェクトリテラルがあります。

var firstObject =
{
    x: 0,
    y: 1,
    z: 2,

    a: 10,
    b: 20,
    e: 30
}

var secondObject =
{
    x: 0,
    y: 1,
    z: 2,

    a: 10,
    c: 20,
    d: 30
}

これら2つのオブジェクトリテラルが持つキーの共通部分を取得したいと思います。

var intersectionKeys  = ['x', 'y', 'z', 'a']

私は明らかにループを行い、同じ名前のキーが他のオブジェクトに存在するかどうかを確認できますが、これがいくつかの関数型プログラミングとマップ/フィルター/使用量の削減に適しているのだろうか?私自身はそれほど機能的なプログラミングを行っていませんが、この問題に対するクリーンで賢い解決策が存在する可能性があると感じています。

25
Piwwoli

indexOfのないソリューション。

var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
    secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };

function intersection(o1, o2) {
    return Object.keys(o1).concat(Object.keys(o2)).sort().reduce(function (r, a, i, aa) {
        if (i && aa[i - 1] === a) {
            r.Push(a);
        }
        return r;
    }, []);
}

document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');

O(n)の2回目の試行。

var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
    secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };

function intersection(o1, o2) {
    return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}

document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
29
Nina Scholz

与えられた答えは素晴らしく驚くべきものですが、 voidanswer に問題がある可能性があります。つまり、「プロパティ値は意図的にundefinedに設定されています。 "

Ninaanswer は良い(本当に素晴らしい)が、JavaScriptの明日の端にいるので、私のものはそれほど悪くないと思う:

var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30 }
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 }

function intersect(o1, o2){
    return Object.keys(o1).filter(k => k in o2)
}

document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');

更新

onalbi 合理的なコメントでパフォーマンスの問題に言及したため、以下のコードが問題を処理するためのより良い方法のようです:

var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30};
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30};

function intersect(o1, o2) {

  const [k1, k2] = [Object.keys(o1), Object.keys(o2)];
  const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2];
  return first.filter(k => k in next);
}

document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
17
Morteza Tourani

私が提案する手順は次のとおりです。

  1. オブジェクトの1つに対してObject.keys()を使用して、キーのarrayを取得します。
  2. .filterを使用して配列の共通部分を見つけ、2番目のオブジェクトに最初の配列に一致するキーが含まれているかどうかを確認します。
var firstObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  b: 20,
  e: 30
}

var secondObject = {
  x: 0,
  y: 1,
  z: 2,

  a: 10,
  c: 20,
  d: 30
}

function getIntKeys(obj1, obj2){

    var k1 = Object.keys(obj1);
    return k1.filter(function(x){
        return obj2[x] !== undefined;
    });
  
}

alert(getIntKeys(firstObject, secondObject));
4
void

再帰関数

これは他の解決策であり、おそらくあなたを助けるでしょう。再帰関数を使用して、2つのオブジェクトをインターセプトしました。このソリューションの利点は、同時にオブジェクトである属性について心配する必要がないことです。

この場合、関数は両方のオブジェクトに存在する属性をインターセプトし、属性の最終値がインターセプトされるように「objSource」の値に割り当てます。

{
        function interceptObjects(objSource, objInterface) {
            let newObj = {};
            for (const key in objSource) {
                if (objInterface.hasOwnProperty(key)) {
                    // in javascript an array is a object too.
                    if (objSource[key] instanceof Object && !Array.isArray(objSource[key]) && objInterface[key] instanceof Object && !Array.isArray(objInterface[key])) {
                        newObj[key] = {};
                        newObj[key] = interceptObjects(objSource[key], objInterface[key])
                    } else {
                        newObj[key] = objSource[key];
                    }

                }
            }
            return newObj;
        }
        
        
        // FOR TESTING


    let objSource = {
            attr1: '',
            attr2: 2,
            attr3: [],
            attr4: {
                attr41: 'lol',
                attr42: 12,
                attr43: 15,
                attr45: [1, 4],
            },
            attr5: [2, 3, 4],
        };


        let objInterface = {
            attr1: null,
            attr4: {
                attr41: null,
                attr42: 12,
                attr45: [1],
            },
            attr5: [],
            attr6: null,
        };


        console.log(this.interceptObjects(objSource, objInterface));
    }
1
Amn

これは、非常に機能的な単純なエントリであり、任意の数のオブジェクトを処理し、渡された最初のオブジェクトから一致するキーの値を返します。

この動作は、誰かがそれを検索している場合に備えて、PHP)のarray_intersect_key()の動作に似ています。

function intersectKeys(first, ...rest) {
    const restKeys = rest.map(o => Object.keys(o));
    return Object.fromEntries(Object.entries(first).filter(entry => restKeys.every(rk => rk.includes(entry[0]))));
}

より良い説明とコメントのためにここに展開しました

function intersectKeys(first, ...rest) {
    // extract the keys of the other objects first so that won't be done again for each check
    const restKeys = rest.map(o => Object.keys(o));
    // In my version I am returning the first objects values under the intersect keys
    return Object.fromEntries(
        // extract [key, value] sets for each key and filter them, Object.fromEntries() reverses this back into an object of the remaining fields after the filter
        Object.entries(first).filter(
            // make sure each of the other object key sets includes the current key, or filter it out
            entry => restKeys.every(
                rk => rk.includes(entry[0])
            )
        )
    );
    // to get JUST the keys as OP requested the second line would simplify down to this
    return Object.keys(first).filter(key => restKeys.every(rk => rk.includes(key)));
}

このソリューションは文字列キーでのみ機能し、シンボルキーは無視され、最終オブジェクトには何も含まれないことに注意することが重要です。シンボルの交差を比較するために同様の関数を書くこともできますが。

0
kyrandita