array.reduce通过递归实现

时间:2018-05-29 10:45:59

标签: javascript algorithm

我试图通过递归实现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);
}




但验证者不接受我的回答。我做错了什么?

3 个答案:

答案 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;
&#13;
&#13;

this引用对函数是私有的,并且调用者无法看到,因此使用它来传播当前状态(迭代器)似乎是理想的。替代方案是:

  • 使用额外参数
  • 向数组或回调函数参数添加属性。

最后一个选项的缺点是调用者可以检测到这种突变。在调用者冻结这些对象的极端情况下,代码甚至会失败并出现异常。

所有解决方案都有一个缺陷:原始呼叫者可以通过初始呼叫传递状态。因此,对于每个解决方案,调用者可以传递索引(或迭代器)的特定值,如下所示:

  • 传递一个带有值的额外参数 - 让我们说5.
  • 将传递给index的函数的特殊reduce属性设置为值5.
  • 使用reducecall致电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