使用Javascript数组计算集合差异的最快或最优雅的方法是什么?
设使用Javascript数组计算集合差异的最快或最优雅的方法是什么?,javascript,arrays,set-difference,Javascript,Arrays,Set Difference,设A和B为两组。我正在寻找非常快速或优雅的方法来计算它们之间的集差(A-B或A\B,取决于您的偏好)。正如标题所说,这两个集合作为Javascript数组存储和操作 注: 壁虎特有的把戏没问题 我更愿意坚持使用本机函数(但如果轻量级库更快的话,我愿意使用它) 我已经看过了,但还没有测试过(见上一点) 编辑:我注意到一条关于包含重复元素的集合的评论。当我说“set”时,我指的是数学定义,这意味着(除其他外)它们不包含重复的元素。如果不知道这是否是最有效的,但可能是最短的 A = [1, 2,
A
和B
为两组。我正在寻找非常快速或优雅的方法来计算它们之间的集差(A-B
或A\B
,取决于您的偏好)。正如标题所说,这两个集合作为Javascript数组存储和操作
注:
- 壁虎特有的把戏没问题
- 我更愿意坚持使用本机函数(但如果轻量级库更快的话,我愿意使用它)
- 我已经看过了,但还没有测试过(见上一点)
编辑:我注意到一条关于包含重复元素的集合的评论。当我说“set”时,我指的是数学定义,这意味着(除其他外)它们不包含重复的元素。如果不知道这是否是最有效的,但可能是最短的
A = [1, 2, 3, 4];
B = [1, 3, 4, 7];
diff = A.filter(function(x) { return B.indexOf(x) < 0 })
console.log(diff);
这是可行的,但我认为另一个要短得多,也很优雅
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;
A=[1',A',b',12];
B=['a',3,4,'B'];
差异集={
ar:{},
diff:Array(),
remove_set:function(a){ar=a;返回this;},
移除:功能(el){
如果(ar.indexOf(el),您可以使用对象作为贴图,以避免对a
的每个元素进行线性扫描,如:
非标准用于获取唯一的属性名称;如果所有元素都已具有唯一的字符串表示形式(如数字),则可以通过删除toSource()来加快代码速度
调用。结合Christoph的思想,假设数组和对象/散列(每个
和朋友)上有两个非标准迭代方法,我们可以在线性时间内得到集合差、并集和交集,总共大约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);
}
};
这假设每个
和过滤器
都是为数组定义的,并且我们有两种实用方法:
myUtils.keys(散列)
:返回
具有散列的键的数组
myUtils.select(散列、fnSelector、,
fnEvaluator)
:返回带有
调用fnEvaluator的结果
在其中的键/值对上
fnSelector
返回true
select()
的灵感来源于普通的Lisp,只是将filter()
和map()
合并为一个(最好在对象.prototype
上定义它们,但这样做会破坏jQuery,所以我决定使用静态实用方法)
性能:使用
var a = [], b = [];
for (var i = 100000; i--; ) {
if (i % 2 !== 0) a.push(i);
if (i % 3 !== 0) b.push(i);
}
给出两个集合,分别包含50000和66666个元素。使用这些值,A-B大约需要75毫秒,而union和INTERSION每个大约需要150毫秒。(Mac Safari 4.0,使用Javascript日期计时。)
我认为这对于20行代码来说是不错的回报。我将散列数组B,然后保留数组A中不存在的值:
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;
}
函数getHash(数组){
//将数组散列为一组属性
//
//参数:
//array-(array)(!nil)要散列的数组
//
//返回:(对象)
//哈希对象,数组中的每个值都有一个属性设置为true
var hash={};
对于(var i=0;i,使用jQuery,最短值为:
var A=[1,2,3,4];
VarB=[1,3,4,7];
var diff=$(A).非(B);
console.log(diff.toArray());
7年后,使用object非常简单(但仍然不如A-B
紧凑),据报道,对于大型阵列,它比indexOf
更快:
console.clear();
设a=新集合([1,2,3,4]);
设b=新集合([5,4,3,2]);
设a_减_b=新集([…a]。过滤器(x=>!b.has(x));
设b_减a=新集([…b].filter(x=>!a.has(x));
让a_相交于_b=新集([…a]。过滤器(x=>b.has(x));
console.log([…a_减去b])//{1}
console.log([…b_减去a])//{5}
log([…a_intersect_b])/{2,3,4}
使用(函数JS库)
至于快速方式,这并不是很优雅,但我已经运行了一些测试来确定。将一个数组作为对象加载要比大量处理快得多:
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);
但是,这仅适用于字符串。如果您计划比较编号集,则需要使用parseFloat一些简单函数映射结果,借用@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 }
看看这些解决方案中的一大群,它们在小的情况下很好。但是,当你把它们放大到一百万个项目时,时间复杂性开始变得愚蠢
A.filter(v => B.includes(v))
这看起来像是一个O(N^2)解决方案。既然有一个O(N)解决方案,让我们使用它,如果您的JS运行时不是最新的,那么您可以轻松地修改为不是生成器
function *setMinus(A, B) {
const set = new Set(A);
for (const v of B) {
if (!set.delete(v)) {
yield v;
}
}
for (const v of set.values()) {
yield v;
}
}
a = [1,2,3];
b = [2,3,4];
console.log(Array.from(setMinus(a, b)));
虽然这比许多其他解决方案要复杂一些,但当您有大的列表时,这将快得多。如果您使用的是Set
s,它可能非常简单且性能良好:
函数设置差异(a、b){
返回新的集合(Array.from(a).filter(item=>!b.has(item));
}
<> > <代码> SET//C++ >使用Hash函数*在引擎盖下,<代码>具有比<代码>索引> <代码>要快得多的速度(这是很重要的,如果你有100个以上的项目),你使用的这个“设置差异”术语是什么?是C++还是别的什么?你的集合中有什么?(例如数字),计算集差可以非常快速和优雅地完成。如果你的集包含(比如)DOM元素,你将陷入一个缓慢的indexOf
实现中。@Crescent:我的集包含数字-很抱歉没有指定。@Josh:这是数学中的标准集运算()@JoshStodola那是@MattBall不,我看到了。但是Josh的问题是有效的,没有答案,所以我回答:)这和我半小时前发布的算法完全一样ago@Christoph:你是对的…我没有注意到这一点。不过我觉得我的实现更容易理解
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 }
A.filter(v => B.includes(v))
function *setMinus(A, B) {
const set = new Set(A);
for (const v of B) {
if (!set.delete(v)) {
yield v;
}
}
for (const v of set.values()) {
yield v;
}
}
a = [1,2,3];
b = [2,3,4];
console.log(Array.from(setMinus(a, b)));