Keep track of how many times a recursive function was called

后端 未结 9 1903
醉梦人生
醉梦人生 2021-02-02 05:03



        
相关标签:
9条回答
  • 2021-02-02 05:41

    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

    0 讨论(0)
  • 2021-02-02 05:44

    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])
    
    0 讨论(0)
  • 2021-02-02 05:45

    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
    
    0 讨论(0)
  • 2021-02-02 05:46

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

    0 讨论(0)
  • 2021-02-02 05:47

    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)
    
    0 讨论(0)
  • 2021-02-02 05:52

    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

    0 讨论(0)
提交回复
热议问题