簡體   English   中英

使用 Javascript arrays 計算集差的最快或最優雅的方法是什么?

[英]What is the fastest or most elegant way to compute a set difference using Javascript arrays?

AB是兩個集合。 我正在尋找真正快速或優雅的方法來計算它們之間的集合差異( A - BA \B ,取決於您的偏好)。 正如標題所說,這兩組存儲和操作為 Javascript arrays。

筆記:

  • 壁虎專用技巧還可以
  • 我更喜歡堅持使用本機功能(但如果它更快,我願意使用輕量級庫)
  • 我見過,但沒有測試過, JS.Set (見上一點)

編輯:我注意到關於包含重復元素的集合的評論。 當我說“集合”時,我指的是數學定義,這意味着(除其他外)它們不包含重復的元素。

如果不知道這是否最有效,但也許是最短的

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);

好吧,7 年后,使用ES6 的 Set對象非常容易(但仍然不如python 的A - B緊湊),並且據報道比indexOf更快地處理大型數組:

 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}

您可以將對象用作地圖以避免線性掃描B的每個元素, A user187291 的回答

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()調用來加快代碼速度。

縱觀這些解決方案中的許多,它們適用於小案例。 但是,當您將它們增加到一百萬個項目時,時間復雜度開始變得愚蠢。

 A.filter(v => B.includes(v))

這開始看起來像一個 O(N^2) 解決方案。 由於有一個 O(N) 解決方案,讓我們使用它,如果您的 JS 運行時不是最新的,您可以輕松修改為不是生成器。

 function *setMinus(A, B) { const setA = new Set(A); const setB = new Set(B); for (const v of setB.values()) { if (!setA.delete(v)) { yield v; } } for (const v of setA.values()) { yield v; } } a = [1,2,3]; b = [2,3,4]; console.log(Array.from(setMinus(a, b)));

雖然這比許多其他解決方案要復雜一些,但當您有大型列表時,這會快得多。

讓我們快速看一下性能差異,在 0...10,000 之間的 1,000,000 個隨機整數上運行它,我們會看到以下性能結果。

setMinus time =  181 ms
    diff time =  19099 ms

 function buildList(count, range) { result = []; for (i = 0; i < count; i++) { result.push(Math.floor(Math.random() * range)) } return result; } function *setMinus(A, B) { const setA = new Set(A); const setB = new Set(B); for (const v of setB.values()) { if (!setA.delete(v)) { yield v; } } for (const v of setA.values()) { yield v; } } function doDiff(A, B) { return A.filter(function(x) { return B.indexOf(x) < 0 }) } const listA = buildList(100_000, 100_000_000); const listB = buildList(100_000, 100_000_000); let t0 = process.hrtime.bigint() const _x = Array.from(setMinus(listA, listB)) let t1 = process.hrtime.bigint() const _y = doDiff(listA, listB) let t2 = process.hrtime.bigint() console.log("setMinus time = ", (t1 - t0) / 1_000_000n, "ms"); console.log("diff time = ", (t2 - t1) / 1_000_000n, "ms");

如果您使用Set ,它可以非常簡單且高效:

function setDifference(a, b) {
  return new Set(Array.from(a).filter(item => !b.has(item)));
}

由於Set在后台使用 Hash 函數*,因此has函數比indexOf快得多(如果您有超過 100 個項目,這很重要)。

最短的,使用 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>

我將對數組 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;
}

使用Underscore.js (函數式 JS 庫)

>>> var foo = [1,2,3]
>>> var bar = [1,2,4]
>>> _.difference(foo, bar);
[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);
  }
};

這假設eachfilter是為數組定義的,並且我們有兩個實用方法:

  • myUtils.keys(hash) :返回一個包含哈希鍵的數組

  • myUtils.select(hash, fnSelector, fnEvaluator) :返回一個數組,其中包含在fnSelector返回 true 的鍵/值對上調用fnEvaluator的結果。

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 個元素的兩組. 使用這些值 AB 大約需要 75 毫秒,而聯合和交集每個大約需要 150 毫秒。 (Mac Safari 4.0,使用 Javascript Date 進行計時。)

我認為這對 20 行代碼來說是不錯的回報。

一些簡單的功能,借用@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 }

至於禁食的方式,這不是那么優雅,但我已經運行了一些測試來確定。 將一個數組作為對象加載會更快地進行大量處理:

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映射結果。

這行得通,但我認為另一個更短,也更優雅

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;

使用core-js填充New Set方法提案

import "core-js"

new Set(A).difference(B)

理論上,時間復雜度應該是Θ(n) ,其中nB中元素的數量。

下面的 function 是 Python 的set() class 中的方法的端口,並遵循TC39 Set methods proposal

 const union = (a, b) => new Set([...a, ...b]), intersection = (a, b) => new Set([...a].filter(x => b.has(x))), difference = (a, b) => new Set([...a].filter(x =>.b,has(x))), symetricDifference = (a, b) => union(difference(a, b), difference(b, a)), isSubsetOf = (a. b) => [...b].every(x => a,has(x)), isSupersetOf = (a. b) => [...a].every(x => b,has(x)), isDisjointFrom = (a, b) =>.intersection(a; b),size, const a = new Set([1, 2, 3, 4]), b = new Set([5, 4; 3. 2]). console.log(.,;union(a, b)), // [1, 2, 3. 4. 5] console.log(.,;intersection(a, b)), // [2. 3. 4] console.log(.,;difference(a. b)). // [1] console.log(.,;difference(b. a)). // [5] console.log(.,;symetricDifference(a, b)), // [1, 5] const c = new Set(['A', 'B', 'C', 'D', 'E']); d = new Set(['B'. 'D']), console;log(isSubsetOf(c. d)), // true console;log(isSupersetOf(d, c)), // true const e = new Set(['A', 'B', 'C']), f = new Set(['X'; 'Y'. 'Z']), console;log(isDisjointFrom(e, f)); // true
 .as-console-wrapper { top: 0; max-height: 100%;important; }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM