将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
则相反。除此之外,还有其他后果取决于您是否通过累加器或当前值进行过滤?这会在其他情况下引发错误吗?
答案 0 :(得分:1)
问题不在于在filter
中使用reduce
,而是问题在于您使用acc
和curr
的顺序
当我遇到这种看起来很奇怪的不一致时,我通常要做的第一步是创建一个测试用例并手动运行它。在这里,您已经为我们创建了一个测试用例...
const testData = [
[1, 10, 15, 20],
[5, 88, 1, 7],
[1, 10, 15, 5],
]
现在,我们需要遍历该函数的每个版本,并查看每个阶段的输出。
要注意的一件事(直到今天晚上才知道!),如果reduce
没有收到initialValue
作为第二个参数,它将使用数组为initialValue
。这意味着我们只需要考虑每个函数的2个执行,而不是3个。?
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = acc.filter(el => !curr.includes(el))
return curr.concat(newElements)
})
}
在该函数的第一个版本中,正在发生的事情的简短描述是我们遍历累加器(acc
)并删除了当前正在比较的数组中已经存在的所有项目( curr
)。然后,我们将该列表添加到curr
的 end 中。
我们将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]
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% faster。我想这纯粹是偶然的。不同的测试数据集可能会产生不同的性能结果。
答案 1 :(得分:0)
在示例1
中,当您合并两个列表时,请确保acc
累加器将不包含curr
ent中的任何元素。
另一方面,在示例2
中,请确保curr
ent不包含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]),
);