4

I'm trying to wrap my head around recursive functional programming concepts.

Consider the following scenario (yes, I could just use .length to get the array's 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);
})

I would like to rephrase this so that I'm not setting the variable count to 0, then mutating the value each time I iterate through my array. Can I set up some sort of function that recursively calls itself and then returns out of the function with the right value?

5 Answers5

1

If you absolutely must test the length of an array recursively, you could do it like this:

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

Please note that this is a really, really silly idea.

The idea is that, on each recursion, we slice off one element from the array (the last one, using slice(0,-1)) and return 1 more than whatever calling arrLen on the rest of the array returned.

The sequence looks something like this:

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
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • better `function arrLen(arr){ return 0 in arr? 1+arrLen(arr.slice(1)): 0; }` or `function arrLen(arr){ function iter(i, count){ return i in arr? iter(i+1, len+1): len } return iter(0, 0) }` so you don't have to `slice` the array. – Thomas Jan 21 '17 at 00:05
  • I'm actually working in Lua, where I'm using this function -- http://stackoverflow.com/questions/2705793/how-to-get-number-of-entries-in-a-lua-table#answer-2705804 But, I'd like to explore recursion and think better in javascript. –  Jan 21 '17 at 00:07
  • @Thomas Part of the idea of the question is that we don't have a `count` variable. And it could certainly be written more tersely, but I'm trying to be clear, not concise. – lonesomeday Jan 21 '17 at 00:09
  • @Thomas But you're right, `"0" in arr` is a much better test. – lonesomeday Jan 21 '17 at 00:10
  • @lonesomeday, no, part of the question was that we don't have an enclosed `count` variable that is incremented/mutated in a loop. In my code examples there is no mutation, just a function argument to pass through the current count, so this is a tail call that can be properly optimized. – Thomas Jan 21 '17 at 00:14
  • @Thomas There's a big blue button at the bottom of the page. Feel free to click it if you have a better solution. – lonesomeday Jan 21 '17 at 00:16
1

I just modified your code a bit:

$(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)
})
Hung Cao
  • 3,130
  • 3
  • 20
  • 29
  • Hmm, this is getting there. Maybe something like: https://jsfiddle.net/mupordLv/ -- But, I don't like testing for `if (!arr[count])`... I'd like to get through the list via an `Each` or a `For in`, although I'm not sure how to exit out of the recursive loop if I do that. –  Jan 21 '17 at 00:14
  • Yeah, that works too. I didn't know that you must call `arrLen(a)` – Hung Cao Jan 21 '17 at 00:18
  • what do you mean by getting through the list? Why do you need that? – Hung Cao Jan 21 '17 at 00:20
  • Can you think of a way to pull this off without testing for an out of bounds array index that fails? My constraints here are that I can run a function for each array value, so essentially I just need to return the number of times my function has been executed. –  Jan 21 '17 at 00:20
0

For computing one value reduce is what to use. It's really not that special. Imagine you want to sum every element instead.

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

And reduce you can actually implement with recursion:

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

Note that an actual implementation wouldn't have used recursion since JS doesn't optimize tail calls and thus a looping construct would be more effiecent. Eg. Ramda is a functional library that provides means of making programs using compositions, but mapping and reductions in Ramda is implemented with loops.

EDIT

In the event you structure really isn't an array but something like a linked list then you need to check if you are at the end by checking if this is the singleton empty element. Here is an example implementation of a singly linked list with reduce, which looks thike the previous in this answer except the accessors and stop condition:

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)
Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • This returns the sum of the items of the array rather than the length of the array. Without knowing the length of the array beforehand for the exit condition, is it possible to use this sort of recursive form? (I'm ultimately playing around in a version of Lua where you usually use this sort of form for determining an array length via a mutable count variable: http://stackoverflow.com/questions/2705793/how-to-get-number-of-entries-in-a-lua-table#answer-2705804/) –  Jan 21 '17 at 03:40
  • @arby For arrays the length is always known so finding that value by going though it would always use the length. With structuresd like singly linked lists the length is not an easy property since you can always increase the length by adding a new elements in front. See the example for a comparison. – Sylwester Jan 21 '17 at 12:43
  • Consider the case with Lua, where I'm using a Table as an array, and I can iterate through the items but I don't know the number of items until I count them. It seems as though the mutating `count` variable is the best way to pull this off short of testing for array index that don't exist. –  Jan 21 '17 at 18:24
0

If you want to use a for/forEach loop, then there's no place for recursion. Iteration and recursion are used to achieve the same result - you should choose one or the other.

Iteration:

Similar to your code just a bit simpler. Keep a count, increment and return it.

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([]));

Recursion:

Check if the next element in the array exists and if it does, keep calling the function recursively with the next index. The function returns the final index + 1, except for 0 which is special-cased. (Note well that this will not work for sparse arrays, as noted in the comments.)

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([]));
skyline3000
  • 7,639
  • 2
  • 24
  • 33
0

You can do this using without using any for loop.

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);

First, I am trying to get the base case. My recursion will stop working once it finds that there is no element left in my array. When there is no element left in my array, then array[0] === undefined. I have initialized the length to zero so that I can increase my length value after each iteration and I did that using length++. My other target is to remove the first element from the array after each iteration, that's why I used the slice method. Then I used these new values of array and length in the returned function.

Mithhu
  • 145
  • 1
  • 7