64

 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

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Edward S
  • 820
  • 1
  • 7
  • 12
  • Can you clarify? Where do you need that tracking? Because you already have three items in the console, hence there are three invocations. – VLAZ Jan 02 '20 at 22:30
  • i need to somehow log to the console that there were 3 recursions. – Edward S Jan 02 '20 at 22:31
  • 1
    Can you show your code with the counter ? – Axnyff Jan 02 '20 at 22:31
  • just edited my code to show the counter – Edward S Jan 02 '20 at 22:34
  • 4
    `.map(Number)` is redundant since the `*` operator coerces the values to number anyway. ;-) – RobG Jan 02 '20 at 22:48
  • 4
    A couple of questions: 1) How do you intend on dealing with negative numbers? For instance, the number `-57` is really a `-50` and a `-7` .. when looked at this way, it would do a reduction of `-5` x `-7` yielding a positive number `35`. Or do you want it to see only the negative sign with the `5` and not the `7`, even tho the `7` is actually negative as well. 2) How do you intend on dealing with numbers which include a zero? as this will automatically zero out the reduction. Therefore, the larger number you pass in, the more likely it will zero out. The other option would be to skip the zeros – Pimp Trizkit Jan 03 '20 at 13:06
  • 3
    I realize my above questions are not about counting the recursion, but rather just the puzzle solving aspect of content used in this question. Please, forgive me. – Pimp Trizkit Jan 03 '20 at 13:08
  • I wonder if you want to know the number of times the function has been called (as you've asked), or the recursion depth (which you seem to want). Because your function can only call itself once per execution, these are the same. But for recursive functions that can call themselves multiple times the two are different (number of times & depth). – CJ Dennis Jan 05 '20 at 01:32
  • @PimpTrizkit the puzzle i was trying to solve deals exclusively with positive integers. I wasn't thinking about negatives. – Edward S Jan 05 '20 at 10:30
  • @CJDennis could you elaborate the difference between the number of times the function was called and the recursion depth? I thought the amount of times the function was (for lack of better word) "recursed" is the same amount of times the function is called. I guess you could say though that the function is called 1 more time then the "recursed" count as it is called before the recursion starts as well. – Edward S Jan 05 '20 at 10:33
  • @chs242 Imagine a recursive function that gets the names of a person's ancestors. They have 2 parents, 4 grandparents, 8 great-grandparents, etc. So a depth of 3 is called 14 times, 7 each for the paternal and maternal sides. – CJ Dennis Jan 05 '20 at 14:00
  • @CJDennis I didn't think of it like that. you are totally correct. In my question I wanted the recursion depth. Thanks for the clarification! – Edward S Jan 06 '20 at 15:54
  • 3
    I'm flattered that you like my answer, but for practical purposes, I think https://stackoverflow.com/a/59570894/1346276 is the cleanest general variant. – phipsgabler Jan 06 '20 at 16:39
  • 2
    @phipsgabler anyone who takes the time to write an intelligent and coherent answer deserves a like. Thank you – Edward S Jan 06 '20 at 18:53

9 Answers9

76

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)
AncientSwordRage
  • 7,086
  • 19
  • 90
  • 173
Sheraff
  • 5,730
  • 3
  • 28
  • 53
  • 6
    awesome. It looks like my counter wasn't working because I declared it in the function – Edward S Jan 02 '20 at 22:39
  • 7
    @chs242 scope rules would dictate that declaring it in the function would create a new one each invocation. https://stackoverflow.com/questions/500431/what-is-the-scope-of-variables-in-javascript – Taplar Jan 02 '20 at 22:40
  • 10
    @chs242 it's not that you declared it within the function. Technically that's all default parameters are doing as well - in your case it's simply that the value never carried over to the next time the function was recursively called. a.e. every time the function runs `counter` would get scrapped and set to `0`, *unless* you explicitly carry it over in your recursive call as Sheraff does. A.e. `singleDigit(number, ++counter)` – zfrisch Jan 02 '20 at 22:50
  • 2
    right @zfrisch I understand that now. Thanks for taking the time to explain it – Edward S Jan 02 '20 at 23:07
  • 35
    Please change `++counter` to `counter+1`. They're functionally equivalent, but the latter specifies intent better, doesn't (unnecessarily) mutate and parameter, and doesn't have the possibility of accidentally post-incrementing. Or better yet, since it's a tail-call, use a loop instead. – BlueRaja - Danny Pflughoeft Jan 03 '20 at 09:36
38

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.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • 1
    The counter variable is only available within the `singleDigit` function and provides a alternative clean way of doing this without passing an argument imo. +1 – AndrewL64 Jan 02 '20 at 22:59
  • 1
    Since `recursion` is now completely isolated it should be totally safe to pass the counter as the last parameter. I don't think creating an inner function is necessary. If you don't like the idea of having parameters for the sole benefit of the recursion (I do take the point that the user could mess with those) then lock them up with `Function#bind` in a partially applied function. – customcommander Jan 03 '20 at 00:41
  • @customcommander Yes, I mentioned this in summary in the first part of my answer -- `the traditional solution is to pass the count as a parameter`. This is an alternate solution in a language that has closures. In some ways it is simpler to follow because it is only one variable instead of a possibly infinite number of variable instances. In other ways knowing this solution helps when the thing you're tracking is a shared object (imagine constructing a unique map) or a very large object (such as an HTML string) – slebetman Jan 03 '20 at 10:11
  • `counter--` would be the traditional way of solving your claim of "cannot be called twice correctly" – MonkeyZeus Jan 03 '20 at 15:54
  • 1
    @MonkeyZeus What difference does that make? Also, how would you know what number to initialize the counter to seeing that it is the count that we want to find? – slebetman Jan 03 '20 at 23:37
28

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

phipsgabler
  • 20,535
  • 4
  • 40
  • 60
  • 6
    To the downvoters: I really agree with you that this is not the best practical solution -- that is why I prefixed it as "purely academic". – phipsgabler Jan 08 '20 at 09:17
  • Honestly, it's an awesome solution and totally suited for the regression / math type of the original question. – Sheraff Jan 09 '20 at 23:09
22

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
customcommander
  • 17,580
  • 5
  • 58
  • 84
6

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])
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • 2
    Another great answer ;) Please note that when _n_ is negative, `digitProduct` will return `NaN` (`-39 ~> ('-' * '3') * '9'`). So you may want to use an absolute value of _n_ and use `-1` or `1` as the initial value of your reduce. – customcommander Jan 03 '20 at 15:23
  • @customcommander: actually, it will return `{"digit":-39,"steps":[-39],"calls":0}`, since `-39 < 9`. While I agree that this might do with some error-checking: *is the parameter a number?* - *is it a positive integer?* - etc. I don't think I'll update to include that. This captures the algorithm, and error-handling is often specific to one's code-base. – Scott Sauyet Jan 03 '20 at 18:26
5

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);
Shehan Dhaleesha
  • 627
  • 1
  • 10
  • 30
Kholiavko
  • 334
  • 3
  • 11
  • 1
    But this way the counter keeps counting on the next call, it needs to be reset on each initial call. It leads to a difficult question: how to tell when a recursive function is called from a different context, in this case global vs function. – RobG Jan 02 '20 at 22:49
  • This is just example in order to come up with a thought. It might be modified by asking user for his needs. – Kholiavko Jan 02 '20 at 22:53
  • @RobG I don't understand your question. The recursive function cannot be called outside of the closure because it is an inner function. So there is no possibility or need to differentiate context because there is only one possible context – slebetman Jan 02 '20 at 22:56
  • @slebetman The counter is never reset. The function returned by `singleDigitDecorator()` will keep incrementing the same counter every time it is called. – customcommander Jan 02 '20 at 23:00
  • @RobG, first that come on mind is pass second boolean params when we call function recursively. And if we don`t have it - reset our counter. – Kholiavko Jan 02 '20 at 23:01
  • @customcommander Ah. OK. I see it. It's like calling `new RegExp()` with `g` option – slebetman Jan 02 '20 at 23:01
  • 1
    @slebetman—the problem is that the function returned by *singleDigitDecorator* does not reset its counter when it's called again. That is the function that needs to know when to reset the counter, otherwise a new instance of the function is required for each use. A possible use case for [*Function.caller*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller)? ;-) – RobG Jan 02 '20 at 23:04
  • @RobG I modified a snippet. – Kholiavko Jan 02 '20 at 23:07
  • @Kholiavko—that's one fix. I'm not a fan of hidden parameters as the function may be called with a user–supplied value when they shouldn't be. But this answer is at least a reasonable alternative to the accepted answer. ;-) Though I think it would be preferable to create *singleDigit* using an IIFE rather than declaring *singleDigitDecorator* then manually calling it. – RobG Jan 02 '20 at 23:33
  • @RobG, yes I think your are right, it will be better, but I write in this way for more transparency. – Kholiavko Jan 03 '20 at 17:37
5

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

Community
  • 1
  • 1
Pimp Trizkit
  • 19,142
  • 5
  • 25
  • 39
  • 1
    There's a lot of education value in this answer so thanks for that. `I feel good code is also fast.` I'd say that code quality _has_ to be measured against a predefined set of requirements. If performance isn't one of them then you gain nothing by replacing code that anybody can understand with "fast" code. You wouldn't believe the amount of code I've seen that has been refactored to be performant to the point nobody can understand it anymore (for some reason optimal code tends to also be undocumented ;). Finally be aware that lazy-generated lists allow one to consume items on demand. – customcommander Jan 03 '20 at 15:35
  • Thank you, I think. IMHO, reading the actual math of how to do it was easier to understand for me.. than the `[...num+''].map(Number).reduce((x,y)=> {return x*y})` or even `[...String(num)].reduce((x,y)=>x*y)` statements that I am seeing in most answers here. So, to me, this had the added benefit of better understanding of whats going on at each iteration *and* much faster. Yes, minified code (which has its place) is terribly hard to read. But in those cases one is generally consciously not caring about its readability but rather just the final result to cut and paste and move on. – Pimp Trizkit Jan 03 '20 at 19:05
  • Doesn't JavaScript have integer division so you can do the equivalent of C `digit = num%10;` `num /= 10;`? Having to do `num - x` first to remove the trailing digit before dividing is likely to force the JIT compiler to do a separate division from the one it did to get the remainder. – Peter Cordes Jan 04 '20 at 09:43
  • I don't think so.These are `var`s (JS has no `int`s). Therefore, `n /= 10;` will convert `n` to a float if needed. `num = num/10 - x/10` might convert it to a float, which is the long form of the equation. Hence, I have to use the refactored version `num = (num-x)/10;` to keep it an integer.There is no way that I can find in JavaScript that can give you both the quotient and remainder of a single division operation. Also,`digit = num%10; num /= 10;` is two separate statements and thus two separate division operations.It's been a while since I've used C, but I thought it was true there as well. – Pimp Trizkit Jan 04 '20 at 21:18
5

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

Mistermatt
  • 556
  • 9
  • 16
  • console.count wouldn't help as the counter gets reset every time the function is called (as has been explaine in the answers above) – Edward S Jan 06 '20 at 15:58
  • 2
    I don't understand your issue as I have it working in Chrome and Firefox, I added a snippet in my answer – Mistermatt Jan 08 '20 at 10:27
1

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)
Luke Sawczak
  • 313
  • 4
  • 14