Keep track of how many times a recursive function was called

JavascriptRecursion

Javascript Problem Overview


 function singleDigit(num) {
      let counter = 0
      let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

      if(number <= 9){
          console.log(number)
      }else{
          console.log(number)
          return singleDigit(number), counter += 1
      }
   }
singleDigit(39)

The code above takes an integer and reduces it to a single digit by multiplying it by its own digits.

Example is 39.

3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.

The console will log:

27 
14 
4

How do I keep track that the recursive function was called 3 times?

I have tried adding a counter but it fails to update. Would appreciate any help

Javascript Solutions


Solution 1 - Javascript

You should add a counter argument to your function definition:

function singleDigit(num, counter = 0) {
    console.log(`called ${counter} times`)
    //...
    return singleDigit(number, counter+1)
}
singleDigit(39)

Solution 2 - Javascript

The traditional solution is to pass the count as a parameter to the function as suggested by another answer.

However, there is another solution in js. A few other answers suggested simply declaring count outside the recursive function:

let counter = 0
function singleDigit(num) {
  counter++;
  // ..
}

This of course works. However this makes the function non-reentrant (cannot be called twice correctly). In some cases you can ignore this problem and simply make sure you don't call singleDigit twice (javascript is single threaded so it's not too hard to do) but this is a bug waiting to happen if you update singleDigit later to be asynchronous and it also feels ugly.

The solution is to declare the counter variable outside but not globally. This is possible because javascript has closures:

function singleDigit(num) {
  let counter = 0; // outside but in a closure

  // use an inner function as the real recursive function:
  function recursion (num) {
    counter ++
    let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

    if(number <= 9){
      return counter            // return final count (terminate)
    }else{
      return recursion(number)  // recurse!
    }
  }

  return recursion(num); // start recursion
}

This is similar to the global solution but each time you call singleDigit (which is now not a recursive function) it will create a new instance of the counter variable.

Solution 3 - Javascript

This is an almost purely academic variant, but you can use a modified fixed point combinator for this purpose.

Lets shorten and improve your original function a bit:

function singleDigit(n) {
    let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
    return digitProduct <= 9 ? digitProduct : singleDigit(digitProduct);
}

// singleDigit(123234234) == 0

From this variant, we can factor out and curry the recursive call:

function singleDigitF(recur) {
    return function (n) {
        let digitProduct = [...(n + '')].reduce((x, y) => x * y, 1);
        return digitProduct <= 9 ? digitProduct : recur()(digitProduct);
    };
}

This function can now be used with a fixed point combinator; specifically I implemented a Y combinator adapted for (strict) JavaScript as follows:

function Ynormal(f, ...args) {
    let Y = (g) => g(() => Y(g));
    return Y(f)(...args);
}

where we have Ynormal(singleDigitF, 123234234) == 0.

Now comes the trick. Since we have factored out the recursion to the Y combinator, we can count the number of recursions within it:

function Ycount(f, ...args) {
    let count = 1;
    let Y = (g) => g(() => {count += 1; return Y(g);});
    return [Y(f)(...args), count];
}

A quick check in the Node REPL gives:

> Ycount(singleDigitF, 123234234)
[ 0, 3 ]
> let digitProduct = (n) => [...(n + '')].reduce((x, y) => x * y, 1)
undefined
> digitProduct(123234234)
3456
> digitProduct(3456)
360
> digitProduct(360)
0
> Ycount(singleDigitF, 39)
[ 4, 3 ]

This combinator will now work for counting the number of calls in any recursive function written in the style of singleDigitF.

(Note that there's two sources of getting zero as a very frequent answer: numeric overflow (123345456999999999 becoming 123345457000000000 etc.), and the fact that you will almost surely get zero as an intermediate value somewhere, when the size of the input is growing.)

Solution 4 - Javascript

Another approach, since you produce all the numbers, is to use a generator.

The last element is your number n reduced to a single digit number and to count how many times you have iterated, just read the length of the array.

const digits = [...to_single_digit(39)];
console.log(digits);
//=> [27, 14, 4]

<script>
function* to_single_digit(n) {
  do {
    n = [...String(n)].reduce((x, y) => x * y);
    yield n;
  } while (n > 9);
}
</script>


Final thoughts

You may want to consider having a return-early condition in your function. Any numbers with a zero in it will return zero.

singleDigit(1024);       //=> 0
singleDigit(9876543210); //=> 0

// possible solution: String(n).includes('0')

The same can be said for any numbers made of 1 only.

singleDigit(11);    //=> 1
singleDigit(111);   //=> 1
singleDigit(11111); //=> 1

// possible solution: [...String(n)].every(n => n === '1')

Finally, you didn't clarify whether you accept only positive integers. If you accept negative integers then casting them to strings can be risky:

[...String(39)].reduce((x, y) => x * y)
//=> 27

[...String(-39)].reduce((x, y) => x * y)
//=> NaN

Possible solution:

const mult = n =>
  [...String(Math.abs(n))].reduce((x, y) => x * y, n < 0 ? -1 : 1)

mult(39)
//=> 27

mult(-39)
//=> -27

Solution 5 - Javascript

You can use closure for this.

Just simply store counter into the closure of function.

Here is example:

function singleDigitDecorator() {
	let counter = 0;

	return function singleDigitWork(num, isCalledRecursively) {

		// Reset if called with new params 
		if (!isCalledRecursively) {
			counter = 0;
		}

		counter++; // *

		console.log(`called ${counter} times`);

		let number = [...(num + "")].map(Number).reduce((x, y) => {
			return x * y;
		});

		if (number <= 9) {
			console.log(number);
		} else {
			console.log(number);

			return singleDigitWork(number, true);
		}
	};
}

const singleDigit = singleDigitDecorator();

singleDigit(39);

console.log('`===========`');

singleDigit(44);

Solution 6 - Javascript

If you are just trying to count how many times it gets reduced and are not caring about the recursion specifically... you can just remove the recursion. The below code remains faithful to the Original Post as it does not count num <= 9 as needing reduction. Therefore, singleDigit(8) will have count = 0, and singleDigit(39) will have count = 3, just like the OP and accepted answer are demonstrating:

const singleDigit = (num) => {
    let count = 0, ret, x;
    while (num > 9) {
        ret = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            ret *= x;
        }
        num *= ret;
        count++;
        console.log(num);
    }
    console.log("Answer = " + num + ", count = " + count);
    return num;
}

It is unnecessary to process numbers 9 or less (ie. num <= 9). Unfortunately the OP code will process num <= 9 even tho it does not count it. The code above will not process nor count num <= 9, at all. It just passes it thru.

I choose not to use .reduce because doing the actual math was much faster to execute. And, for me, easier to understand.


Further thinking on speed

I feel good code is also fast. If you are using this type of reduction (which is used in numerology a lot) you might be needing to use it on a massive amount of data. In this case, speed will become the upmost of importance.

Using both .map(Number) and console.log (at each reduction step) are both very very long to execute and unnecessary. Simply deleting .map(Number) from the OP sped it up by about 4.38x. Deleting console.log sped it up so much it was almost impossible to properly test (I didn't want to wait for it).

So, similar to customcommander's answer, not using .map(Number) nor console.log and pushing the results into an array and using .length for count is much much faster. Unfortunately for customcommander's answer, using a generator function is really really slow (that answer is about 2.68x slower than the OP without .map(Number) and console.log)

Also, instead of using .reduce I just used the actual math. This single change alone sped up my version of the function by a factor of 3.59x.

Finally, recursion is slower, it takes up stack space, uses more memory, and has a limit to how many times it can "recur". Or, in this case, how many steps of reduction it can use to finish the full reduction. Rolling out your recursion to iterative loops keeps it all on the same place on the stack and has no theoretical limit on how many reduction steps it can use to finish. Thus, these functions here can "reduce" almost any sized integer, only limited by execution time and how long an array can be.

All this in mind...

const singleDigit2 = (num) => {
    let red, x, arr = [];
    do {
        red = 1;
        while (num > 9) {
            x = num % 10;
            num = (num - x) / 10;
            red *= x;
        }
        num *= red;
        arr.push(num);
    } while (num > 9);
    return arr;
}

let ans = singleDigit2(39);
console.log("singleDigit2(39) = [" + ans + "],  count = " + ans.length );
 // Output: singleDigit2(39) = [27,14,4],  count = 3

The above function runs extremely fast. It is about 3.13x faster than the OP (without .map(Number) and console.log) and about 8.4x faster than customcommander's answer. Keep in mind that deleting console.log from the OP prevents it from producing a number at each step of reduction. Hence, the need here to push these results into an array.

PT

Solution 7 - Javascript

There have been many interesting answers here. I think my version offers an additional interesting alternative.

You do several things with your required function. You recursively reduce it to a single digit. You log the intermediate values, and you would like a count of the recursive calls made. One way to handle all this is to write a pure function which will return a data structure that contains the final result, the steps taken and the call count all in one:

  {
    digit: 4,
    steps: [39, 27, 14, 4],
    calls: 3
  }

You can then log the steps if you desire, or store them for further processing.

Here is a version which does that:

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])

console .log (singleDigit (39))

Note that we track the steps but derive the calls. While we could track the call count with an additional parameter, that seems to gain nothing. We also skip the map(Number) step -- these will be coerced to numbers in any case by the multiplication.

If you have concerns about that defaulted steps parameter being exposed as part of your API, it's easy enough to hide it by using an internal function like this:

const singleDigit = (n) => {
  const recur = (n, steps) => 
    n <= 9
      ? {digit: n, steps: [... steps, n], calls: steps .length}
      : recur ([... (n + '')] .reduce ((a, b) => a * b), [... steps, n])
  return recur (n, [])
}

And in either case, it might be a bit cleaner to extract the digit multiplication into a helper function:

const digitProduct = (n) => [... (n + '')] .reduce ((a, b) => a * b)

const singleDigit = (n, steps = []) =>
  n <= 9
    ? {digit: n, steps: [... steps, n], calls: steps .length}
    : singleDigit (digitProduct(n), [... steps, n])

Solution 8 - Javascript

Why not make a call to console.count in your function ?

Edit: Snippet to try in your browser :

function singleDigit(num) {
    console.count("singleDigit");

    let counter = 0
    let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})

    if(number <= 9){
        console.log(number)
    }else{
        console.log(number)
        return singleDigit(number), counter += 1
    }
}
singleDigit(39)

I have it working in Chrome 79 and Firefox 72

Solution 9 - Javascript

Here's a Python version that uses a wrapper function to simplify the counter, as has been suggested by slebetman's answer — I write this only because the core idea is very clear in this implementation:

from functools import reduce

def single_digit(n: int) -> tuple:
    """Take an integer >= 0 and return a tuple of the single-digit product reduction
    and the number of reductions performed."""

    def _single_digit(n, i):
        if n <= 9:
            return n, i
        else:
            digits = (int(d) for d in str(n))
            product = reduce(lambda x, y: x * y, digits)
            return _single_digit(product, i + 1)

    return _single_digit(n, 0)

>>> single_digit(39)
(4, 3)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEdward SView Question on Stackoverflow
Solution 1 - JavascriptSheraffView Answer on Stackoverflow
Solution 2 - JavascriptslebetmanView Answer on Stackoverflow
Solution 3 - JavascriptphipsgablerView Answer on Stackoverflow
Solution 4 - JavascriptcustomcommanderView Answer on Stackoverflow
Solution 5 - JavascriptKholiavkoView Answer on Stackoverflow
Solution 6 - JavascriptPimp TrizkitView Answer on Stackoverflow
Solution 7 - JavascriptScott SauyetView Answer on Stackoverflow
Solution 8 - JavascriptMistermattView Answer on Stackoverflow
Solution 9 - JavascriptLuke SawczakView Answer on Stackoverflow