Javascript配列を使用してセットの差を計算する最も高速または最もエレガントな方法は何ですか?


103

させてAB2つのセットになります。それらの間のセットの差(または好みに応じて)を計算する本当に高速またはエレガントな方法を探しています。タイトルが示すように、2つのセットはJavascript配列として保存および操作されます。A - BA \B

ノート:

  • Gecko固有のトリックは大丈夫です
  • ネイティブ関数にこだわるのが好きです(ただし、高速な場合は軽量ライブラリを利用できます)
  • 私はJS.Setを見ましたが、テストしていません(前のポイントを参照)

編集:重複する要素を含むセットに関するコメントに気付きました。「セット」と言うとき、私は数学的定義を指しています。これは、(特に)重複する要素が含まれていないことを意味します。


あなたが使用しているこの「セット差」の用語は何ですか?それはC ++か何かですか?
Josh Stodola、

あなたのセットには何がありますか?対象とするタイプ(数値など)に応じて、セット差の計算は非常に高速かつエレガントに実行できます。セットに(たとえば)DOM要素が含まれていると、低速なindexOf実装に行き詰まります。
クレセントフレッシュ

@クレセント:私のセットには数字が含まれています-指定しないと申し訳ありません。@Josh:それは数学の標準セットの操作(だen.wikipedia.org/wiki/Set_%28mathematics%29#Complements
マット・ボール


1
@マットボールいや、私はそれを見た。しかし、ジョシュの質問は有効で答えがなかったので答えました:)
Pat

回答:


173

これが最も効果的かどうかわからないが、おそらく最短

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(function(x) { return B.indexOf(x) < 0 })

console.log(diff);

ES6に更新:

A = [1, 2, 3, 4];
B = [1, 3, 4, 7];

diff = A.filter(x => !B.includes(x) );

console.log(diff);

8
+1:最も効率的なソリューションではないが、間違いなく短くて読みやすい
Christoph

10
注:array.filterは、ブラウザー間でサポートされていません(IEなどではサポートされていません)。@Gattは「Gecko固有のトリックは大丈夫」と言ったので、それは重要ではないようですが、言及する価値があると思います。
エリックブレシェミエ2009年

44
これは非常に遅いです。O(| A | * | B |)
glebm 2013

1
@EricBréchemierこれは現在サポートされています(IE 9以降)。Array.prototype.filterは、標準のECMAScript機能です。
Quentin Roy

5
ES6 !B.includes(x)では、B.indexOf(x) < 0:)の代わりに使用できます
c24w

86

さて、7年後、ES6のSetオブジェクトを使用すると、それは非常に簡単です(ただし、Python ほどコンパクトではありませんA - BindexOf

console.clear();
let a = new Set([1, 2, 3, 4]);
let b = new Set([5, 4, 3, 2]);


let a_minus_b = new Set([...a].filter(x => !b.has(x)));
let b_minus_a = new Set([...b].filter(x => !a.has(x)));
let a_intersect_b = new Set([...a].filter(x => b.has(x))); 

console.log([...a_minus_b]) // {1}
console.log([...b_minus_a]) // {5}
console.log([...a_intersect_b]) // {2,3,4}


1
また、大きな配列の場合、indexOfよりもかなり高速です。
Estus Flask 2016

100
JavaScriptセットにunion / intersect / differenceが組み込まれていない理由は私を超えています...
SwiftsNamesake 2016年

6
同意します; これらは、jsエンジンに実装された低レベルのプリミティブである必要があります。それも私を超えています...
ラファエル

4
@SwiftsNamesake 2018年1 にgithub.com/tc39/agendas/blob/master/2018/01.mdで話題になると思われる、組み込みのメソッドのセットに関する提案があります。
ジョン

15

オブジェクトをマップとして使用して、user187291の回答のようにのB各要素を線形スキャンしないようにすることができます。A

function setMinus(A, B) {
    var map = {}, C = [];

    for(var i = B.length; i--; )
        map[B[i].toSource()] = null; // any other value would do

    for(var i = A.length; i--; ) {
        if(!map.hasOwnProperty(A[i].toSource()))
            C.push(A[i]);
    }

    return C;
}

非標準のtoSource()メソッドは、一意のプロパティ名を取得するために使用されます。すべての要素がすでに一意の文字列表現を持っている場合(数値の場合のように)、toSource()呼び出しを削除することでコードを高速化できます。


9

jQueryを使用した場合の最短は次のとおりです。

var A = [1, 2, 3, 4];
var B = [1, 3, 4, 7];

var diff = $(A).not(B);

console.log(diff.toArray());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


差分のオブジェクトを返します。
ドリューベイカー、

2
jQuery notは、3.0.0-rc1以降、汎用オブジェクトで動作しなくなりました。参照してくださいgithub.com/jquery/jquery/issues/3147
マルク=アンドレ・Lafortune

2
それは〜70Kサードパーティのライブラリに依存関係を追加するために素晴らしいアイデアではありませんだけで、ここで他の回答のように同じことが、わずか数行のコードで実現することができるので、これを実行します。ただし、プロジェクトですでにjQueryを使用している場合は、これで問題ありません。
CBarr

このアプローチではコードが少なくなりますが、さまざまなアルゴリズムの空間と時間の複雑さ、およびメソッドの実行に使用するデータ構造の説明は提供されません。データのスケールアップやメモリの制限が許可されている場合、開発者が評価なしでソフトウェアを設計できるように、ブラックボックス化されています。大きなデータセットでこのようなアプローチを使用すると、ソースコードをさらに調査するまで、パフォーマンスは不明のままになる可能性があります。
ダウンヒル

これは、BにないAの要素の量(この場合は2)を返すだけです。2を配列に変換しても意味がありません...
Alex

6

配列Bをハッシュし、配列Aの値をBに存在しないようにします。

function getHash(array){
  // Hash an array into a set of properties
  //
  // params:
  //   array - (array) (!nil) the array to hash
  //
  // return: (object)
  //   hash object with one property set to true for each value in the array

  var hash = {};
  for (var i=0; i<array.length; i++){
    hash[ array[i] ] = true;
  }
  return hash;
}

function getDifference(a, b){
  // compute the difference a\b
  //
  // params:
  //   a - (array) (!nil) first array as a set of values (no duplicates)
  //   b - (array) (!nil) second array as a set of values (no duplicates)
  //
  // return: (array)
  //   the set of values (no duplicates) in array a and not in b, 
  //   listed in the same order as in array a.

  var hash = getHash(b);
  var diff = [];
  for (var i=0; i<a.length; i++){
    var value = a[i];
    if ( !hash[value]){
      diff.push(value);
    }
  }
  return diff;
}

これは、30分前に投稿したアルゴリズムとまったく同じです
Christoph

@クリストフ:あなたは正しい…私はそれに気づかなかった。私は私の実装を理解する方が簡単だと思います:)
EricBréchemier'09 / 11/13

複数回再利用できるように、getDifferenceの外でdiffを計算する方が良いと思います。多分そのようにオプション:getDifference(a, b, hashOfB)、渡されない場合は計算され、そうでない場合はそのまま再利用されます。
Christophe Roussy

4

Christophのアイデアを組み込み、配列およびオブジェクト/ハッシュ(eachおよびフレンド)でいくつかの非標準の反復メソッドを想定すると、合計約20行の線形時間で差、和集合、交差を設定できます。

var setOPs = {
  minusAB : function (a, b) {
    var h = {};
    b.each(function (v) { h[v] = true; });
    return a.filter(function (v) { return !h.hasOwnProperty(v); });
  },
  unionAB : function (a, b) {
    var h = {}, f = function (v) { h[v] = true; };
    a.each(f);
    b.each(f);
    return myUtils.keys(h);
  },
  intersectAB : function (a, b) {
    var h = {};
    a.each(function (v) { h[v] = 1; });
    b.each(function (v) { h[v] = (h[v] || 0) + 1; });
    var fnSel = function (v, count) { return count > 1; };
    var fnVal = function (v, c) { return v; };
    return myUtils.select(h, fnSel, fnVal);
  }
};

これは、eachおよびfilterが配列に対して定義されていること、および2つのユーティリティメソッドがあることを前提としています。

  • myUtils.keys(hash):ハッシュのキーを持つ配列を返します

  • myUtils.select(hash, fnSelector, fnEvaluator):true fnEvaluatorfnSelector返すキーと値のペアを呼び出した結果の配列を 返します。

select()緩くCommon Lispのに触発されて、単にさfilter()map()一つにロールバックされます。(それらをで定義することObject.prototypeをお勧めしますが、そうすることでjQueryで大混乱が発生するため、静的ユーティリティメソッドを使用しました。)

パフォーマンス:テスト

var a = [], b = [];
for (var i = 100000; i--; ) {
  if (i % 2 !== 0) a.push(i);
  if (i % 3 !== 0) b.push(i);
}

50,000要素と66,666要素の2つのセットを提供します。これらの値では、ABは約75msかかりますが、unionと交差はそれぞれ約150msです。(Mac Safari 4.0、タイミングにJavaScript日付を使用)

これは、20行のコードにとって適切な見返りだと思います。


1
hasOwnProperty()要素が数値であっても確認する必要があります。それ以外の場合、Object.prototype[42] = true;平均など42の結果は結果セットでは決して発生しません
Christoph

そのように42を設定することは可能ですが、誰かが実際にそうするような半現実的な使用例はありますか?しかし、一般的な文字列については、私がポイントをとります-それは、いくつかのObject.prototype変数または関数と簡単に競合する可能性があります。
jg-faustus


3

@milanの答えを借りたいくつかの単純な関数:

const setDifference = (a, b) => new Set([...a].filter(x => !b.has(x)));
const setIntersection = (a, b) => new Set([...a].filter(x => b.has(x)));
const setUnion = (a, b) => new Set([...a, ...b]);

使用法:

const a = new Set([1, 2]);
const b = new Set([2, 3]);

setDifference(a, b); // Set { 1 }
setIntersection(a, b); // Set { 2 }
setUnion(a, b); // Set { 1, 2, 3 }

2

断食の方法については、これはそれほどエレガントではありませんが、確かにいくつかのテストを実行しました。1つの配列をオブジェクトとしてロードすると、大量の処理がはるかに高速になります。

var t, a, b, c, objA;

    // Fill some arrays to compare
a = Array(30000).fill(0).map(function(v,i) {
    return i.toFixed();
});
b = Array(20000).fill(0).map(function(v,i) {
    return (i*2).toFixed();
});

    // Simple indexOf inside filter
t = Date.now();
c = b.filter(function(v) { return a.indexOf(v) < 0; });
console.log('completed indexOf in %j ms with result %j length', Date.now() - t, c.length);

    // Load `a` as Object `A` first to avoid indexOf in filter
t = Date.now();
objA = {};
a.forEach(function(v) { objA[v] = true; });
c = b.filter(function(v) { return !objA[v]; });
console.log('completed Object in %j ms with result %j length', Date.now() - t, c.length);

結果:

completed indexOf in 1219 ms with result 5000 length
completed Object in 8 ms with result 5000 length

ただし、これは文字列でのみ機能します。番号付きセットを比較する場合は、結果をparseFloatでマップする必要があります。


1
b.filter(function(v) { return !A[v]; });2番目の関数ではc = にすべきではありませんか?
fabianmoronzirfas

あなたは正しいです。どういうわけか私にとってはさらに速いようです
SmujMaiku

1

これは機能しますが、別の方がはるかに短く、エレガントでもあると思います

A = [1, 'a', 'b', 12];
B = ['a', 3, 4, 'b'];

diff_set = {
    ar : {},
    diff : Array(),
    remove_set : function(a) { ar = a; return this; },
    remove: function (el) {
        if(ar.indexOf(el)<0) this.diff.push(el);
    }
}

A.forEach(diff_set.remove_set(B).remove,diff_set);
C = diff_set.diff;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.