[英]What is the most elegant way of partly copying object arrays in JavaScript
[英]What is the fastest or most elegant way to compute a set difference using Javascript arrays?
設A
和B
是兩個集合。 我正在尋找真正快速或優雅的方法來計算它們之間的集合差異( A - B
或A \B
,取決於您的偏好)。 正如標題所說,這兩組存儲和操作為 Javascript arrays。
筆記:
編輯:我注意到關於包含重復元素的集合的評論。 當我說“集合”時,我指的是數學定義,這意味着(除其他外)它們不包含重復的元素。
如果不知道這是否最有效,但也許是最短的
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);
}
};
這假設each
和filter
是為數組定義的,並且我們有兩個實用方法:
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;
下面的 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.