44
_.difference([], [])

このメソッドは、次のようなプリミティブ型のデータがある場合に正常に機能します

var a = [1,2,3,4];
var b = [2,5,6];

そして、_.difference(a,b)呼び出しは戻ります[1,3,4]

しかし、私が次のようなオブジェクトを使用している場合

var a = [{'id':1, 'value':10}, {'id':2, 'value':20}];
var b = [{'id':1, 'value':10}, {'id':4, 'value':40}];

うまくいかないようです

4

8 に答える 8

31

受け入れられた答えは正しく、他の答えも良いアイデアを提供しますが、アンダースコアを使用して実装するのが非常に簡単な追加のオプションがあります。

このソリューションは、各オブジェクトが一意の ID を持つことに依存していますが、多くの場合、これは当てはまり、わずか 2 行のコードでオブジェクトの 2 つの配列の違いを取得できます。

アンダースコアの "pluck" メソッドを使用すると、ソース セットとターゲット セットのすべての ID の配列をすばやく作成できます。そこから、アンダースコアのすべての配列メソッド、差分、結合、交差点などが機能します...

操作の後、必要なソース リストからオブジェクトのリストを取得するのは簡単です。次に例を示します。

詳細:

var a = [{'id':1, 'value':10}, {'id':2, 'value':20}];
var b = [{'id':1, 'value':10}, {'id':4, 'value':40}];

var arr1 = _.pluck(a, "id");
var arr2 = _.pluck(b, "id");
var diff = _.difference(arr1, arr2);
var result = _.filter(a, function(obj) { return diff.indexOf(obj.id) >= 0; });

または、より簡潔に:

var diff = _.difference(_.pluck(a, "id"), _.pluck(b, "id"));
var result = _.filter(a, function(obj) { return diff.indexOf(obj.id) >= 0; });

もちろん、これと同じ手法を拡張して、任意の配列メソッドで使用できます。

于 2013-12-27T08:45:35.623 に答える
16

理由は単純に、同じコンテンツを持つオブジェクトは同じオブジェクトではないということです。

var a = [{'id':1, 'value':10}, {'id':2, 'value':20}]; 
a.indexOf({'id':1, 'value':10})

別のオブジェクトを検索しているため、0 ではなく -1 が返されます。

ソース コードhttp://underscorejs.org/underscore.jsを参照し_.differenceてください。_.contains

_.difference = function(array) {
  var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
  return _.filter(array, function(value){ return !_.contains(rest, value); });
};

_.contains 最終的に使用するため、indexOf同じオブジェクトを指していない限り、オブジェクトは見つかりません。

すべてのアイテムをループし、比較コールバックを呼び出すことでアンダースコアを改善でき_.containsます。これは、difference または contains 関数に渡すことができます。また、contains メソッドを改善するこのバージョンを確認することもできます。

于 2012-10-30T20:56:52.990 に答える
3

@kontr0l アプローチを他のアプローチよりも使用したい状況を実際に想像できますが、このアプローチは 2 次であることを理解する必要があるため、基本的にこのコードは単純なアプローチの抽象化であり、2 つの配列のすべての値を反復処理します。

二次式よりも優れたアプローチがあります。ここでは大きな O 表記は使用しませんが、ここに 2 つの主なアプローチを示します。どちらも素朴なアプローチよりも優れています。

  • 配列の 1 つを繰り返し処理し、二分探索を使用して、並べ替えられた 2 番目の配列に存在するかどうかを確認します。
  • set/hash/dictionary/名前を付けて値を入れます。

すでに述べたように、differenceメソッドのより柔軟なアナログを使用して標準メソッドを再実装する場合、最初のアプローチをオブジェクトに採用できますindexOf

2 番目のアプローチでは、2015 年 2 月の時点で、最新のブラウザーのみがSetsをサポートしているという事実で壁にぶつかることがあります。JavaScript のハッシュ (つまりオブジェクト) では、文字列型のキーしか持てないため、キーとして呼び出されたオブジェクトは、最初にtoStringメソッドを介して変換する必要があります。したがって、いくつかの => 通信を提供する必要があります。ほとんどの場合、実際には非常に簡単です。たとえば、特定の例では、そのような対応はString(obj.id).

このような対応があれば、次の lodas/undercore アプローチも使用できます。

var idsA = _.pluck(a, 'id');
var idsB = _.pluck(b, 'id');

// actually here we can stop in some cases, because 
// quite often we need to identify object, but not the object itself - 
// for instance to send some ids through remote API.
var intersect = _.intersection(idsA, idsB);

//to be 100% sure you get the idea, here we assume that object having equal ids are treated as equal, so does not really matter which of arrays we'll iterate:

var dictA = _.object(idsA, a); // now we can find a by id faster then with _.find
var intersectObj = intersect.map(function(id) {return dictA[id})

しかし、少し厳しい制限を認めて購入します - セットオブジェクトと自然数の間の対応を構築することができ、さらに効率的なアルゴリズムを構築できます。つまり、すべての id は非負の整数です。より効率的なアルゴリズムを使用できます。

秘訣は、次のように 2 つのヘルパー配列を導入して set を実装することです。

var naturalSet = function (arr) {
    var sparse = [];
    var dense = [];

    var contains = function (i) {
        var res = sparse[i] < dense.length && dense[sparse[i]] == i;
        return res;
    }

    var add = function (v) {
        if (!contains(v)) {
            sparse[v] = dense.length;
            dense.push(v);
        }
    }

    arr.forEach(add);

    return {
        contains: contains,
        toArray: function () {
            return dense
        },
        _getDense: function () {
            return dense
        },
        _getSparse: function () {
            return sparse
        }
    }
}

次に、naturalSet へのマッピングを使用してセットを導入できます。

var set = function (arr, valueOf) {
    var natSet = naturalSet(arr.map(valueOf));
    return {
        contains: function (item) {
            return natSet.contains(valueOf(item))
        },
        toArray: function () {
            var sparse = natSet._getSparse();
            var res = natSet._getDense().map(function (i) {
                return arr[sparse[i]];
            });
            return res;
        }
    }
}

最後に、交差を導入できます。

var intersection = function(arr1, arr2, valueOf) {
   return set(arr2.filter(set(arr1, valueOf).contains), valueOf).toArray();
}

そのため、作業中のデータの構造に依存すると、役立つ場合があります。

于 2015-02-16T12:08:08.590 に答える
0

ここで遅く飛び込んで申し訳ありませんが、これが役立つかもしれません:

array_of_objects = 
    // return the non-matching items (without the expected properties)
    _.difference(array_of_objects,
        // filter original list for items with expected properties
        _.where(
            // original list
            array_of_objects,
            // expected properties
            {'id':1, 'value':10}
        )
    )
于 2014-02-17T19:21:07.313 に答える