【问题标题】:Trying to learn recursion, don't quite understand scope试图学习递归,不太了解范围
【发布时间】:2017-09-07 09:27:30
【问题描述】:

function isNumber(arr) {
    var result = [];
    if (arr.length > 1) {
        if (typeof arr[0] === 'number') {
            result.push(arr[0]);
            arr.splice(0, 1);
            isNumber(arr);
        } else {
            arr.splice(0, 1);
            isNumber(arr);
        }
    } else {
        console.log(result);
    }

}

isNumber([1,'2','3',2]);

我正在尝试创建一个遍历数组的函数,如果第一项是数字,它将删除它并将其推送到名为 result 的数组中。然后它再次调用自己,直到数组为空。

它当前返回一个空数组,调试器显示结果数组不断重置回一个空数组。

我明白为什么会发生这种情况,但我正试图弄清楚如何解决它。

【问题讨论】:

  • 每次调用都会丢失结果,但不会返回。
  • 您必须将结果数组作为参数传递。你的arr.length > 1 应该是arr.lenght > 0
  • 这个例子的真正问题是使用递归是没有意义的。一个简单的 for 循环就可以了。
  • @kevinSpaceyIsKeyserSöze:不应该 >= 1 吗?如果它 >= 0 则在检索 arr[0] 时会出错
  • 如果最终目标是学习递归......去斐波那契......这对递归来说真的没有意义

标签: javascript arrays recursion


【解决方案1】:

要了解递归,您必须了解递归:)

  • 定义终端条件(array.length 为 0)
  • 定义步骤(您在当前步骤中执行的操作)如何更深入。

function isNumber(arr) {
  // terminate recursion
  if (arr.length === 0) return []

  // step
  var first = arr[0],
    rest = arr.slice(1)

  // if first is a number concat it with the rest else next step
  return typeof first === 'number'? [first].concat(isNumber(rest)) : isNumber(rest) 
}

console.log(isNumber([1, '2', '3', 2]));

【讨论】:

    【解决方案2】:

    您可以通过两种不同的方式进行操作。这个需要随身携带结果数组,但可以进行尾调用优化:

    function onlyNumbers(arr, result) {
      if (typeof result === "undefined") result = [];
      if (arr.length == 0) return result;
      let element = arr.shift();
      if (typeof element === "number") {
        result.push(element);
      }
      return onlyNumbers(arr, result);
    }
    

    我在这里使用了一个技巧来仅在第一次调用中初始化结果数组。在更严格的语言中,我会使用两个不同的函数:一个(非递归)只是用(arr, []) 调用另一个函数。在第一个循环之后,每次调用都会将结果数组向前传递,并填充每个递归调用,然后调用堆栈展开,result 不做任何更改地返回。

    这种模式正好相反(概念上更简单,因为您不需要额外的参数,但不能很好地优化):

    function onlyNumbers(arr) {
      if (arr.length == 0) return [];
      let element = arr.shift();
      let result = onlyNumbers(arr);
      if (typeof element === "number") {
        result.unshift(element);
      }
      return result;
    }
    

    在这里,我们不预先构造结果数组,而是在堆栈展开时将结果元素(向后!)附加到返回值。

    当然,递归是函数式编程的基础。在许多函数式编程语言中,没有其他方法可以循环,只有递归。但这并不意味着递归仍然是原始操作:通常这些语言会基于递归构建其他更复杂的操作,然后使用这些操作来代替裸递归以达到很好的效果。其中最基本的是mapfilterreduce

    一旦您了解了递归代码的工作原理,您可能想了解filter 是如何通过递归实现的,以及如何在 JavaScript 中使用更简单的单行代码:

    [1,'2','3',2].filter(x => typeof x === "number")
    # => [1, 2]
    

    【讨论】:

    • 谢谢阿马丹!您是否碰巧知道任何可以让我学习函数式编程的好资源?我已经尝试过 Youtube,但它非常有限。
    • 递归的好处是避免中间值存储在局部变量中,只使用调用栈。您的任何方法都没有避免它们。
    【解决方案3】:

    你需要存储结果并返回它。

    function isNumber(arr) {
        var result = typeof arr[0] === 'number' ? [arr[0]] : [];
        arr.shift();
        return arr.length ? result.concat(isNumber(arr)) : result;
    }
    
    console.log(isNumber([1, '2', '3', 2]));
    console.log(isNumber([]));

    【讨论】:

      【解决方案4】:

      只需对您的代码进行少量更改:

      function isNumber(arr, result) {
          if (arr.length > 0) {
              if (typeof arr[0] === 'number') {
                  result.push(arr[0]);
                  arr.splice(0, 1);
                  return isNumber(arr, result);
              } else {
                  arr.splice(0, 1);
                  return isNumber(arr, result);
              }
          } else {
              console.log(result);
          }
      
      }
      
      isNumber([1,'2','3',2], []);

      【讨论】:

        【解决方案5】:

        结果应该在函数之外创建,因为现在每个函数调用都会创建一个新的空结果并使用新的,而不是旧的,然后你可以检查函数的工作

            var result = [];
            function isNumber(arr) {            
                if (arr.length > 1) {
                    if (typeof arr[0] === 'number') {
                        result.push(arr[0]);
                        arr.splice(0, 1);
                        isNumber(arr);
                    } else {
                        arr.splice(0, 1);
                        isNumber(arr);
                    }
                } else {
                    console.log(result);
                }
        
            }
        
            isNumber([1,'2','3',2]);
            console.log(result);
        

        如果你想要函数返回一个带有结果的数组,你应该将数组作为函数的参数传递,然后在函数调用后检查它

            function isNumber(arr, result) {            
                if (arr.length > 1) {
                    if (typeof arr[0] === 'number') {
                        result.push(arr[0],result);
                        arr.splice(0, 1);
                        isNumber(arr);
                    } else {
                        arr.splice(0, 1);
                        isNumber(arr,result);
                    }
                } 
        
            }
        
          var result=[];
          isNumber([1,'2','3',2], result);
        
          console.log(result);
        

        【讨论】:

          【解决方案6】:

          在这个问题上使用递归没有任何意义。如果您真的想这样做,那么下面的示例就是一种方法。

          每次调用函数时都需要传递结果数组,以便在数组中存储一个新变量,以便在最后返回。在您当前的示例中,它会不断覆盖变量。

          function isNumber(arr, res) {
            // If there is no res set, create a new array
            var result = res || [];
            
            // Base case, return the result if the arr is empty
            if (arr.length <= 0) {
              console.log(result);
              return result;
            }
            
            // Get the first element from the array (and remove it from the array)
            var firstElement = arr.shift();
          
            // If it's a number, add it to the array
            if (typeof firstElement === 'number') {
              result.push(firstElement);
            }
            
            isNumber(arr, result);
          }
          
          isNumber([1, '2', '3', 2]);

          【讨论】:

          • 嗯,确实如此。如果您在一般情况下考虑它,过滤数据结构非常适合递归方案。
          • 当然我的意思是在这种情况下具体。使用过滤器可以更轻松地实现结果,例如:[1, '2', '3', 2].filter(function(el) { return typeof el === 'number'; });
          • OP 正在尝试学习递归。过滤数组(即使有一个内置的方法可以做到这一点)是一个非常有效的学习示例。尽管有array.length,即使以递归方式计算数组长度也可以学习
          • 我同意,这就是为什么我提供了带有 cmets 的示例代码,以通过递归解决这种情况,这可能有助于 OP 更好地理解它
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-08
          • 1970-01-01
          • 2018-08-25
          • 1970-01-01
          相关资源
          最近更新 更多