ES6仍然有while
这种类型的函数很容易因为过多的处理而导致长时间的延迟。如果无条件地甚至是优先使用ES6和数组方法,如reduce、filter等,而不是像while和for这样简单的老式循环,那么情况就更加明显了。
当计算许多集合的交集时,如果发现某个项不属于交集,则每次迭代的工作量应该减少。因为forEach不能中断,所以你被迫继续迭代所有元素。添加一些代码来避免搜索当前项是否属于交集,可以提高性能,但这是一个真正的补救措施。
还有一种倾向就是只为了从数组、集合或映射中删除一个单独的项而创建整个新数据集。这是一个非常糟糕的习惯,我看到越来越多的人采用ES5的方式。
获取n个集合的交集。
那么问题来了,如何找到许多集合的交集。
解决方案B
一个典型的ES6解决方案
function intersectB(firstSet, ...sets) {
var intersect = (a,b) => {
return new Set([...a].filter(item => b.has(item)))
};
sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
return firstSet;
}
var sets = [new Set([1,2,3,4]), new Set([1,2,4,6,8]), new Set([1,3,4,6,8])];
var inter = intersectB(...sets);
console.log([...inter]);
这个功能在执行简单测试时表现良好,执行时间不到一毫秒。但在我看来,它是一个浪费内存的低效结构。几乎在每一行创建数组和集合,并在已知结果的情况下迭代整个集合。
让我们给它更多的工作。100个集合,每个集合最多有10000个项目,每个测试有10个不同数量的匹配项。大多数拦截将返回空集合。
警告:会导致页面挂起一整秒钟... :(
function createLargeSet(count,odds){
var numbers = new Set();
while(count-- > 0){
if(Math.random() < odds){
numbers.add(count);
}
}
return numbers;
}
function bigArrayOfSets(setCount,setMaxSize,odds){
var bigSets = [];
for(var i = 0; i < setCount; i ++){
bigSets.push(createLargeSet(setMaxSize,odds));
}
return bigSets;
}
function intersectB(firstSet, ...sets) {
var intersect = (a,b) => {
return new Set([...a].filter(item => b.has(item)))
};
sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
return firstSet;
}
var testSets = [];
for(var i = 0.1; i <= 1; i += 0.1){
testSets.push(bigArrayOfSets(100,10000,i));
}
var now = performance.now();
testSets.forEach(testDat => intersectB(...testDat));
var time = performance.now() - now;
console.log("Execution time : " + time);
解决方案A
一种更好的方式,不如花哨,但更加高效。
function intersectA(firstSet,...sets) {
var count = sets.length;
var result = new Set(firstSet);
firstSet.forEach(item => {
var i = count;
var allHave = true;
while(i--){
allHave = sets[i].has(item)
if(!allHave) { break }
}
if(!allHave){
result.delete(item);
}
})
return result;
}
比较
现在让我们来比较一下两者,如果你感到幸运的话,可以尝试猜测性能差异,这是衡量你对JavaScript执行理解的好方法。
function createLargeSet(count,odds){
var numbers = new Set();
while(count-- > 0){
if(Math.random() < odds){
numbers.add(count);
}
}
return numbers;
}
function bigArrayOfSets(setCount,setMaxSize,odds){
var bigSets = [];
for(var i = 0; i < setCount; i ++){
bigSets.push(createLargeSet(setMaxSize,odds));
}
return bigSets;
}
function intersectA(firstSet,...sets) {
var count = sets.length;
var result = new Set(firstSet);
firstSet.forEach(item => {
var i = count;
var allHave = true;
while(i--){
allHave = sets[i].has(item)
if(!allHave) { break }
}
if(!allHave){
result.delete(item);
}
})
return result;
}
function intersectB(firstSet, ...sets) {
var intersect = (a,b) => {
return new Set([...a].filter(item => b.has(item)))
};
sets.forEach(sItem => firstSet = intersect(firstSet, sItem));
return firstSet;
}
var testSets = [];
for(var i = 0.1; i <= 1; i += 0.1){
testSets.push(bigArrayOfSets(100,10000,i));
}
var now = performance.now();
testSets.forEach(testDat => intersectB(...testDat));
var time = performance.now() - now;
console.log("Execution time 'intersectB' : " + time);
var now = performance.now();
testSets.forEach(testDat => intersectA(...testDat));
var time = performance.now() - now;
console.log("Execution time 'intersectA' : " + time);
正如你所看到的,使用简单的while循环可能不如使用filter酷,但性能优势巨大,这是下次编写完美的3行ES6数组操作函数时需要记住的事情。别忘了for和while。
.reduce()
。 - nnnnnn.filter(i => listOfAllOtherSets.every(set => set.has(i)))
怎么样? - Sebastian Simon