Home > OS >  Improve debugging output of a recursive function
Improve debugging output of a recursive function

Time:05-14

I have the following recursive function to count the number of ways change can be returned given various coin denominations:

function makeChange(amount, coins) {
    // Note: using Floats here will not work
    console.log(`Amount: ${amount}, Coins: ${coins}`);
    return (amount === 0)  ? 1 :
           (amount < 0)    ? 0 :
           (!coins.length) ? 0 :
                             makeChange(amount-coins[0], coins) 
                             makeChange(amount, coins.slice(1));
}
console.log(
    makeChange(11, [7,3,1])
);

However, I'd like to improve the visual-aspect of the debugging to really see what might be going on behind the scenes of the recursive function -- almost like each level of the stack and how to show that. I've improved it to pass a level parameter so I can do indentation and I then have:

function makeChange(amount, coins, level=0) {
    console.log(`${' '.repeat(level)}Amount: ${amount}, Coins: ${coins}`);
    return (amount === 0)  ? 1 :
           (amount < 0)    ? 0 :
           (!coins.length) ? 0 :
                             makeChange(amount-coins[0], coins, level 1) 
                             makeChange(amount, coins.slice(1), level 1);
}
console.log(
    makeChange(10, [5,1])
);

But even this is a bit difficult to understand as it has so much superfluous input. What might be a better way to add in various debug helpers to better visualize this?

CodePudding user response:

One possibility is just to also track the returns and use a bit more formatting to group together the events at a single level.

You can run this here, but you will need to open the browser console to see the results, as there are too many lines for StackOverflow's one:

function makeChange(amount, coins, level=0) {
    console.log(`${'|  '.repeat(level)}makeChange(${amount}, [${coins.join (', ')}])`);
  
    const result =  (amount === 0)  ? 1 :
           (amount < 0)    ? 0 :
           (!coins.length) ? 0 :
                             makeChange(amount-coins[0], coins, level 1) 
                             makeChange(amount, coins.slice(1), level 1);
  
console.log(`${'|  '.repeat(Math .max(level - 1, 0))}${level > 0 ? '|  ' : ''} --> ${result}`);        return result
}
console.log(
    makeChange(11, [7, 3, 1])
);
.as-console-wrapper {max-height: 100% !important; top: 0}

It will give you output like this:

makeChange(11, [7, 3, 1])
|  makeChange(4, [7, 3, 1])
|  |  makeChange(-3, [7, 3, 1])
|  |   --> 0
|  |  makeChange(4, [3, 1])
|  |  |  makeChange(1, [3, 1])
|  |  |  |  makeChange(-2, [3, 1])
|  |  |  |   --> 0
|  |  |  |  makeChange(1, [1])
|  |  |  |  |  makeChange(0, [1])
|  |  |  |  |   --> 1
|  |  |  |  |  makeChange(1, [])
|  |  |  |  |   --> 0
|  |  |  |   --> 1
|  |  |   --> 1
|  |  |  makeChange(4, [1])
|  |  |  |  makeChange(3, [1])
|  |  |  |  |  makeChange(2, [1])
|  |  |  |  |  |  makeChange(1, [1])
|  |  |  |  |  |  |  makeChange(0, [1])
|  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  makeChange(1, [])
|  |  |  |  |  |  |   --> 0
|  |  |  |  |  |   --> 1
|  |  |  |  |  |  makeChange(2, [])
|  |  |  |  |  |   --> 0
|  |  |  |  |   --> 1
|  |  |  |  |  makeChange(3, [])
|  |  |  |  |   --> 0
|  |  |  |   --> 1
|  |  |  |  makeChange(4, [])
|  |  |  |   --> 0
|  |  |   --> 1
|  |   --> 2
|   --> 2
|  makeChange(11, [3, 1])
|  |  makeChange(8, [3, 1])
|  |  |  makeChange(5, [3, 1])
|  |  |  |  makeChange(2, [3, 1])
|  |  |  |  |  makeChange(-1, [3, 1])
|  |  |  |  |   --> 0
|  |  |  |  |  makeChange(2, [1])
|  |  |  |  |  |  makeChange(1, [1])
|  |  |  |  |  |  |  makeChange(0, [1])
|  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  makeChange(1, [])
|  |  |  |  |  |  |   --> 0
|  |  |  |  |  |   --> 1
|  |  |  |  |  |  makeChange(2, [])
|  |  |  |  |  |   --> 0
|  |  |  |  |   --> 1
|  |  |  |   --> 1
|  |  |  |  makeChange(5, [1])
|  |  |  |  |  makeChange(4, [1])
|  |  |  |  |  |  makeChange(3, [1])
|  |  |  |  |  |  |  makeChange(2, [1])
|  |  |  |  |  |  |  |  makeChange(1, [1])
|  |  |  |  |  |  |  |  |  makeChange(0, [1])
|  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  makeChange(1, [])
|  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  makeChange(2, [])
|  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  makeChange(3, [])
|  |  |  |  |  |  |   --> 0
|  |  |  |  |  |   --> 1
|  |  |  |  |  |  makeChange(4, [])
|  |  |  |  |  |   --> 0
|  |  |  |  |   --> 1
|  |  |  |  |  makeChange(5, [])
|  |  |  |  |   --> 0
|  |  |  |   --> 1
|  |  |   --> 2
|  |  |  makeChange(8, [1])
|  |  |  |  makeChange(7, [1])
|  |  |  |  |  makeChange(6, [1])
|  |  |  |  |  |  makeChange(5, [1])
|  |  |  |  |  |  |  makeChange(4, [1])
|  |  |  |  |  |  |  |  makeChange(3, [1])
|  |  |  |  |  |  |  |  |  makeChange(2, [1])
|  |  |  |  |  |  |  |  |  |  makeChange(1, [1])
|  |  |  |  |  |  |  |  |  |  |  makeChange(0, [1])
|  |  |  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  |  |  makeChange(1, [])
|  |  |  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  |  makeChange(2, [])
|  |  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  makeChange(3, [])
|  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  makeChange(4, [])
|  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  makeChange(5, [])
|  |  |  |  |  |  |   --> 0
|  |  |  |  |  |   --> 1
|  |  |  |  |  |  makeChange(6, [])
|  |  |  |  |  |   --> 0
|  |  |  |  |   --> 1
|  |  |  |  |  makeChange(7, [])
|  |  |  |  |   --> 0
|  |  |  |   --> 1
|  |  |  |  makeChange(8, [])
|  |  |  |   --> 0
|  |  |   --> 1
|  |   --> 3
|  |  makeChange(11, [1])
|  |  |  makeChange(10, [1])
|  |  |  |  makeChange(9, [1])
|  |  |  |  |  makeChange(8, [1])
|  |  |  |  |  |  makeChange(7, [1])
|  |  |  |  |  |  |  makeChange(6, [1])
|  |  |  |  |  |  |  |  makeChange(5, [1])
|  |  |  |  |  |  |  |  |  makeChange(4, [1])
|  |  |  |  |  |  |  |  |  |  makeChange(3, [1])
|  |  |  |  |  |  |  |  |  |  |  makeChange(2, [1])
|  |  |  |  |  |  |  |  |  |  |  |  makeChange(1, [1])
|  |  |  |  |  |  |  |  |  |  |  |  |  makeChange(0, [1])
|  |  |  |  |  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  |  |  |  |  makeChange(1, [])
|  |  |  |  |  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  |  |  |  makeChange(2, [])
|  |  |  |  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  |  |  makeChange(3, [])
|  |  |  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  |  makeChange(4, [])
|  |  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  |  makeChange(5, [])
|  |  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  |  makeChange(6, [])
|  |  |  |  |  |  |  |   --> 0
|  |  |  |  |  |  |   --> 1
|  |  |  |  |  |  |  makeChange(7, [])
|  |  |  |  |  |  |   --> 0
|  |  |  |  |  |   --> 1
|  |  |  |  |  |  makeChange(8, [])
|  |  |  |  |  |   --> 0
|  |  |  |  |   --> 1
|  |  |  |  |  makeChange(9, [])
|  |  |  |  |   --> 0
|  |  |  |   --> 1
|  |  |  |  makeChange(10, [])
|  |  |  |   --> 0
|  |  |   --> 1
|  |  |  makeChange(11, [])
|  |  |   --> 0
|  |   --> 1
|   --> 4
 --> 6
6
undefined

It's not beautiful, but it's not too bad.

CodePudding user response:

It seems like the following would be useful to potentially print depending on the verbosity level of the script:

  1. The stack level. Of this can be done using tabs or something similar rather than just "Level=1".
  2. Whether the base case is reached or not. For example, the above function might only reach the base case three times out of 1000 recursive function calls.
  3. The variables that the function is applied with.

With the above, we can improve the function to something like the following:

function makeChange(amount, coins, verbose) {
    console.log(`Changing ${amount}...`);
    return (function _makeChange(amount, coins, verbose, debugLevel=0, debugCoinsUsed=[]) {
        if (verbose || amount===0)
        console.log(`${' '.repeat(debugLevel)}Amount: ${amount}, Coins: ${coins}${ amount===0? ' ==> ['  debugCoinsUsed   ']' :''}`);
        return (amount === 0)  ? 1 :
           (amount < 0)    ? 0 :
           (!coins.length) ? 0 :
                             _makeChange(amount-coins[0], coins, verbose, debugLevel 1, [...debugCoinsUsed, coins[0]]) 
                             _makeChange(amount, coins.slice(1), verbose, debugLevel 1, [...debugCoinsUsed]);
    })(amount, coins, verbose);
}
console.log(
    makeChange(10, [5,1]),
    makeChange(10, [5,1], true)

);

And the non-verbose call would log:

makeChange(10, [5,1], false)

Changing 10...
  Amount: 0, AvailableCoins: 5,1 ==> [5,5]
       Amount: 0, AvailableCoins: 1 ==> [5,1,1,1,1,1]
           Amount: 0, AvailableCoins: 1 ==> [1,1,1,1,1,1,1,1,1,1]
3
  • Related