【问题标题】:Recursive javascript array length递归javascript数组长度
【发布时间】:2017-06-06 01:30:29
【问题描述】:

我正试图围绕递归函数式编程概念展开思考。

考虑以下场景(是的,我可以使用.length 来获取数组的长度):

https://jsfiddle.net/ur5gc397/

$(function(){
  function arrLen(arr) {
    var count = 0;

    $.each(arr, function() {
      count = count + 1;
    });

    return count;
  }

  var a = ["a", "b", "c", "d"];

  var lengthOfA = arrLen(a);
})

我想改写一下,这样我就不会将变量 count 设置为 0,然后在每次遍历我的数组时改变值。我可以设置某种函数,递归调用自身,然后以正确的值从函数中返回吗?

【问题讨论】:

  • 如果您不将count 设置为初始值,它将等于undefined
  • var lengthOfA = a.reduce(count => count+1, 0);
  • 无论如何,如果不使用length 属性,就无法处理稀疏数组。
  • const length = arr => arr.shift() ? 1 + length(arr) : 0

标签: javascript recursion functional-programming


【解决方案1】:

我只是稍微修改了你的代码:

$(function(){
  function arrLen(arr, count) {
  if(!arr[count]) return count;    
    count++;

    return arrLen(arr, count);
  }

  var a = ["a", "b", "c", "d"];
  var lengthOfA = arrLen(a, 1);

  $('body').html(lengthOfA)
})

【讨论】:

  • 嗯,这就到了。可能类似于:jsfiddle.net/mupordLv -- 但是,我不喜欢测试if (!arr[count])...我想通过EachFor in 来查看列表,尽管我不确定如果我这样做,如何退出递归循环。
  • 是的,这也有效。我不知道你必须打电话给arrLen(a)
  • 通过列表是什么意思?为什么需要它?
  • 你能想出一种方法来解决这个问题,而无需测试失败的越界数组索引吗?我的限制是我可以为每个数组值运行一个函数,所以基本上我只需要返回我的函数已执行的次数。
【解决方案2】:

如果你绝对必须递归地测试数组的长度,你可以这样做:

function arrLen(arr) {
    if (!("0" in arr)) { // [].length is banned
        return 0;
    }
    return arrLen(arr.slice(0, -1)) + 1;
}

请注意,这是一个非常非常愚蠢的想法

这个想法是,在每次递归时,我们从数组中切出一个元素(最后一个,使用slice(0,-1))并返回1,而不是在返回的数组的其余部分上调用arrLen

序列看起来像这样:

arrLen(["a", "b", "c", "d"])
    calls arrLen(["a", "b", "c"])
        calls arrLen(["a", "b"])
            calls arrLen(["a"])
                calls arrLen([])
                returns 0 == 0
            returns 0 + 1 == 1
        returns 0 + 1 + 1 == 2
    returns 0 + 1 + 1 + 1 == 3
returns 0 + 1 + 1 + 1 + 1 == 4

【讨论】:

  • 更好的function arrLen(arr){ return 0 in arr? 1+arrLen(arr.slice(1)): 0; }function arrLen(arr){ function iter(i, count){ return i in arr? iter(i+1, len+1): len } return iter(0, 0) } 这样你就不必slice 数组了。
  • 我实际上在 Lua 工作,我正在使用这个函数 -- stackoverflow.com/questions/2705793/… 但是,我想探索递归并在 javascript 中更好地思考。
  • @Thomas 这个问题的部分想法是我们没有count 变量。它当然可以写得更简洁,但我想清楚,而不是简洁。
  • @Thomas 但你是对的,"0" in arr 是一个更好的测试。
  • @lonesomeday,不,部分问题是我们没有一个封闭的count 变量,它在循环中递增/变异。在我的代码示例中,没有突变,只是传递当前计数的函数参数,因此这是一个可以适当优化的尾调用。
【解决方案3】:

如果你想使用 for/forEach 循环,那么就没有递归的地方了。迭代和递归用于实现相同的结果 - 您应该选择其中一个。

迭代:

与您的代码类似,只是更简单一些。保持计数,递增并返回。

function arrayLength(array) {
  let count = 0;
  for (const i of array) count++;
  return count;
}

console.log(arrayLength([1, 2, 3, 4, 5]));
console.log(arrayLength([]));

递归:

检查数组中的下一个元素是否存在,如果存在,继续使用下一个索引递归调用函数。该函数返回最终索引 + 1,但特殊情况下的 0 除外。 (请注意,这不适用于 sparse arrays,如 cmets 中所述。)

function arrayLength(array, index) {
  index = index || 0;

  if (array[index+1]) {
    return arrayLength(array, index+1);
  } else if (index === 0) {
    return 0;
  } else {
    return index + 1;
  }
}

console.log(arrayLength([1, 2, 3, 4, 5]));
console.log(arrayLength([]));

【讨论】:

    【解决方案4】:

    为了计算一个值reduce 是使用什么。它真的没有那么特别。想象一下,您想对每个元素求和。

    [1,2,3,4,5].reduce((a,e) => a+e, 0); // => 15
    

    reduce 你实际上可以用递归来实现:

    Array.prototype.myReduce = function(proc,init) {
      var arr = this;
      var nLen = this.length;
      function helper(acc, nIndex) {
        return nIndex >= nLen ?
               acc :
               helper(proc(acc, arr[nIndex]), nIndex+1); 
      }
      return helper(init, 0);
    } 
    
    [1,2,3,4,5].myReduce((a,e) => a+e, 0); // => 15
    

    请注意,实际实现不会使用递归,因为 JS 不会优化尾调用,因此循环构造会更有效。例如。 Ramda 是一个函数库,它提供了使用组合制作程序的方法,但 Ramda 中的映射和归约是通过循环实现的。

    编辑

    如果你的结构真的不是一个数组,而是一个类似链表的东西,那么你需要通过检查这是否是单例空元素来检查你是否在最后。这是一个带有reduce 的单链表的示例实现,除了访问器和停止条件外,它看起来与此答案中的前一个相同:

    function List() {}
    
    List.prototype.cons = function(a) {
      var c = new List();
      c.a = a;
      c.d = this;
      return c;
    }
    
    List.prototype.car = function() {
      return this.a;
    }
    
    List.prototype.cdr = function() {
      return this.d;
    }
    
    List.prototype.reduce = function(proc, init) {
      function helper (lst, acc) {
        return lst === emptyList ? 
               acc :
               helper(lst.cdr(), proc(acc, lst.car()));
      }
      return helper(this, init);
    }
    
    List.prototype.length = function() {
      return this.reduce(acc => acc+1, 0);
    }
    
    List.prototype.reverse = function() {
      return this.reduce((acc, a) => acc.cons(a),emptyList);
    }
    
    // Singleton empty list
    window.emptyList = new List();
    
    var example = emptyList.cons(1).cons(2).cons(9);
    example.reduce((a,e)=>a+e,0); //=> 12
    example.length(); // ==> 3
    example.reverse(); // ==> (1,2,9)
    

    【讨论】:

    • 这将返回数组项的总和,而不是数组的长度。在事先不知道退出条件的数组长度的情况下,是否可以使用这种递归形式? (我最终在 Lua 版本中玩耍,您通常使用这种形式通过可变计数变量确定数组长度:stackoverflow.com/questions/2705793/…
    • @arby 对于数组,长度始终是已知的,因此通过始终使用长度来找到该值。对于结构化的单链表,长度不是一个简单的属性,因为您总是可以通过在前面添加新元素来增加长度。请参阅示例进行比较。
    • 考虑 Lua 的情况,我使用 Table 作为数组,我可以遍历这些项目,但直到我计算它们之前我不知道项目的数量。似乎变异的 count 变量是最好的方法来解决这个问题,而不是测试不存在的数组索引。
    【解决方案5】:

    你可以不使用任何 for 循环来做到这一点。

    function arrLen(array, length=0) {
        if (array[0] === undefined) return length;
        length++;
        const newArray = array.slice(1);
        return arrLen(newArray, length)
    }
    
    var a = ["a", "b", "c", "d"];
    var lengthOfA = arrLen(a);
    

    首先,我想了解基本情况。一旦发现我的数组中没有元素,我的递归将停止工作。当我的数组中没有元素时,然后array[0] === undefined。我已将长度初始化为零,以便在每次迭代后增加长度值,我使用length++ 做到了这一点。我的另一个目标是在每次迭代后从数组中删除第一个元素,这就是我使用 slice 方法的原因。然后我在返回的函数中使用了这些新的数组和长度值。

    【讨论】:

      猜你喜欢
      • 2019-10-29
      • 2021-11-20
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 2015-07-14
      • 2021-09-23
      相关资源
      最近更新 更多