当将Array.prototype.reduce与Array.prototype.filter链接时,当过滤当前值而不是累加器值时(概念上和外壳下)有什么区别?
// function union creates a union of all values that appear among arrays
// example A
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = acc.filter(el => !curr.includes(el));
return curr.concat(newElements);
});
};
console.log(union([1, 10, 15, 20], [5, 88, 1, 7], [1, 10, 15, 5]));
// output (7) [1, 10, 15, 5, 88, 7, 20]
// example B
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = curr.filter(el => !acc.includes(el));
return acc.concat(newElements);
});
};
console.log(union([1, 10, 15, 20], [5, 88, 1, 7], [1, 10, 15, 5]));
//output (7) [1, 10, 15, 20, 5, 88, 7]输出的差异表明,数组的计算顺序是“相反的”。据我所知,在使用arr.filter时,值是从头到尾计算的,而对于curr.filter则相反。此外,如果您通过累加器或当前值进行筛选,它们是否还依赖于其他的后果?这会在不同的上下文中抛出错误吗?
发布于 2020-01-24 05:38:22
问题不在于filter在reduce中的使用,而在于您使用acc和curr的顺序。
当我遇到这种看似奇怪的不一致时,我通常采取的第一步是创建一个测试用例并手动运行它。在这里,你已经为我们创建了一个测试用例.
const testData = [
[1, 10, 15, 20],
[5, 88, 1, 7],
[1, 10, 15, 5],
]现在,我们需要运行每个版本的函数,并查看每个阶段的输出。
有一件事要注意(直到今天晚上我才知道!)如果reduce没有接收到一个initialValue作为第二个参数,它将使用数组中的第一个项作为initialValue。这意味着我们只需要考虑每个函数的2次执行,而不是3次。
例A
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = acc.filter(el => !curr.includes(el))
return curr.concat(newElements)
})
}在该函数的第一个版本中,对正在发生的事情的简短描述是,我们正在循环累加器(acc),并删除当前正在比较的数组(curr)中已经存在的所有项。然后我们将这个列表添加到curr的末尾。
事实上,我们正在将newElements推向curr的末尾,这一点很重要。这就是为什么两个不同版本的订单是不同的。
第一次执行
const acc = [1, 10, 15, 20]
const curr = [5, 88, 1, 7]
const newElements = [10, 15, 20] // these elements exist in acc but not in curr
curr.concat(newElements) === [5, 88, 1, 7, 10, 15, 20]第二次执行
const acc = [5, 88, 1, 7, 10, 15, 20] // carried over from first execution
const curr = [1, 10, 15, 5]
const newElements = [88, 7, 20] // these elements exist in acc but not in curr
curr.concat(newElements) === [1, 10, 15, 5, 88, 7, 20]例B
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = curr.filter(el => !acc.includes(el))
return acc.concat(newElements)
})
}在该函数的第一个版本中,对正在发生的事情的简短描述是,我们正在遍历当前正在比较的数组(curr),并删除累加器(acc)中已经存在的所有项。然后我们将这个列表添加到acc的末尾。
在下面的第一次执行结束时,您已经可以看到结果是以一种非常不同的顺序产生的。
第一次执行
const acc = [1, 10, 15, 20]
const curr = [5, 88, 1, 7]
const newElements = [5, 88, 7] // these elements exist in curr but not in acc
acc.concat(newElements) === [1, 10, 15, 20, 5, 88, 7]第二次执行
const acc = [1, 10, 15, 20, 5, 88, 7] // carried over from first execution
const curr = [1, 10, 15, 5]
const newElements = [] // these elements exist in acc but not in curr
acc.concat(newElements) === [1, 10, 15, 20, 5, 88, 7]结论
简单地回答你的问题是,累加器上的过滤和当前数组之间的区别是,只要输入不同,结果就会不同。♂️
此外,如果您通过累加器或当前值进行筛选,它们是否还依赖于其他的后果?这会在不同的上下文中抛出错误吗?
幸运的是,没有任何关于错误的担忧。但是值得注意的是,您的函数的第二个版本是~10%,而不是第一个版本。我猜这完全是间接的。不同的测试数据集可能产生不同的性能结果。
发布于 2020-01-24 07:52:33
例如,在1中,当您连接这两个列表时,您要确保acc模拟器不会包含来自current的任何元素。
另一方面,在2中,您要确保current不会包含已经存在于acc模拟器中的任何元素。
差异在于元素显示的最终顺序。
我认为这两个示例都没有效率,因为它们都涉及到O(n2)时间复杂性,因为您正在嵌套迭代。第二个,正如其他人所说的,可能会有一些更好的性能,因为嵌套的迭代将在可能比累加器短的块上进行。
我宁愿写得差不多像这样:
const union = (...tuples) => Array.from(
new Set(
tuples.flatMap(n => n),
)
);
console.log(
union([1, 10, 15, 20], [5, 88, 1, 7], [1, 10, 15, 5]),
);
https://stackoverflow.com/questions/59890313
复制相似问题