我试图通过递归实现Array.reduce()方法,它适用于我
var index = 0;
function reduce(arr, fn, initial) {
if (index >= arr.length) {
return initial;
}
if (!initial) {
initial = arr[index];
index++;
}
initial = fn(initial, arr[index], index, arr);
index++;
return reduce(arr, fn, initial);
}

但验证者不接受我的回答。我做错了什么?
答案 0 :(得分:2)
一些问题:
index
定义为永远不会重置为0的全局变量,因此如果多次调用reduce
,它将具有错误的值。有几种解决方案。我建议使用this
值来跟踪你的位置。initial
参数是可选的,但您的代码使用!initial
对此进行测试并不够好:如果initial
的值为0或任何其他假值,则不应该被视为缺席。请改用arguments.length
。initial
参数而增加索引时,您不会检查该索引是否变得太大。永远不应使用无效索引调用回调函数。交换两个if
语句。reduce
将返回undefined
,但它应该抛出错误,就像使用本机reduce
方法一样。以下是处理这些问题的方法:
function reduce(arr, fn, initial) {
// Get iterator for the indexes from `this`.
// The first time it will not be an iterator, so get it from arr.keys().
const iter = Object(this).next ? this : arr.keys();
let item = iter.next();
if (arguments.length < 3) { // Solid check for presence of initial argument
// Throw error when there's nothing to reduce
if (item.done) {
throw new TypeError("reduce of empty array with no initial value");
}
initial = arr[item.value];
item = iter.next();
}
// Check end of array only after having dealt with missing initial value
if (item.done) return initial;
initial = fn(initial, arr[item.value], item.value, arr);
return reduce.call(iter, arr, fn, initial); // Pass on the index via `this`
}
// Some test cases
console.log(reduce([1, 2, 3], (a, b) => a+b, 0), "should be 6");
console.log(reduce([1, 2, 3], (a, b) => a+b, 0), "should be 6");
console.log(reduce([1, 2, 3], (a, b) => a+b), "should be 6");
console.log(reduce([1], (a, b) => a+b), "should be 1");
console.log(reduce([1], (a, b) => a+b, 2), "should be 3");
console.log(reduce([], (a, b) => a+b, 1), "should be 1");
try {
console.log(reduce([], (a, b) => a+b), "error");
} catch(e) {
console.log("Error was raised as expected");
}
&#13;
this
引用对函数是私有的,并且调用者无法看到,因此使用它来传播当前状态(迭代器)似乎是理想的。替代方案是:
最后一个选项的缺点是调用者可以检测到这种突变。在调用者冻结这些对象的极端情况下,代码甚至会失败并出现异常。
所有解决方案都有一个缺陷:原始呼叫者可以通过初始呼叫传递状态。因此,对于每个解决方案,调用者可以传递索引(或迭代器)的特定值,如下所示:
index
的函数的特殊reduce
属性设置为值5. reduce
或call
致电apply
,为其提供特定的this
值(迭代器)。没有什么可以做的,因为要求是使用递归。由于递归要求,函数在递归调用中无法做到原始调用者无法做到的事情。
答案 1 :(得分:1)
一种可能的解决方案是将实际索引放入函数参数中,并检查数组中是否存在索引。
为了进一步查找initialValue
,您需要一个未定义值的符号,以便在未定义值和非设置值之间进行控制。
function reduce(array, fn, initialValue, index) {
index = index || 0;
if (arguments.length === 0) {
throw 'Reduce: No array';
}
if (arguments.length === 1 || typeof fn !== 'function') {
throw 'Reduce: No function';
}
if (arguments.length === 2) {
while (index < array.length && !(index in array)) {
index++;
}
if (index in array) {
return reduce(array, fn, array[index], index + 1)
}
throw 'Reduce: Empty array without initial value';
}
if (index >= array.length) {
return initialValue;
}
return reduce(array, fn, index in array ? fn(initialValue, array[index], index, array) : initialValue, index + 1);
}
// console.log(reduce()); // throws Reduce: No array
// console.log(reduce([])); // throws Reduce: No function
// console.log(reduce([], (a, b) => a + b)); // throws Reduce: Empty array without initial value
console.log(reduce([], (a, b) => a + b, 42));
console.log(reduce([3, 4], (a, b) => a + b));
答案 2 :(得分:1)
正如您所发现的那样,将索引参数放在函数之外会导致复杂性,必须持续管理它才能使函数正常工作。我认为通常,编写函数的赋值假设它们是自包含的而不是依赖于全局变量。
我们可以像这样递归地概念化reduce
:
function reduce(arr, fn, initial){
if (!arr.length)
return initial;
if (arr.length == 1)
return fn(initial, arr[0]);
return reduce(arr.slice(1), fn, fn(initial, arr[0]));
}
console.log(reduce([1,2,3], (a,b)=>a+b, 0)); // 6
在JavaScript中,每次调用slice
都会生成该数组部分的副本,从而使此版本对较大输入的效率降低。提高效率的一个选择可能是有一个内部递归函数,它依赖于遍历数组的两个参数,一个索引和一个累加器,但我不确定验证者会允许它,因为外部函数不会更长的是自称。
在您的代码中需要注意的另一件事是!initial
只要initial
是一个值为&#34; falsy,&#34;不仅当它没有作为参数传递时。
这里&#34; sa&#34; hack&#34; ish(因为它修改了fn
,虽然我们可以修改它:),基于索引的解决方案,不需要额外的参数并使用完整的4个fn
参数:
function reduce(arr, fn, initial){
if (fn.index === undefined)
fn.index = 0;
else
fn.index++;
if (fn.index == arr.length)
return initial;
return reduce(arr, fn, fn(initial, arr[fn.index], fn.index, arr));
}
console.log(reduce([1,2,3], (a,b)=>a+b, 0)); // 6