改进递归函数的调试输出

Improve debugging output of a recursive function

我有以下递归函数来计算在给定各种硬币面额的情况下可以返回零钱的方式的数量:

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

但是,我想改进调试的可视化方面,以真正了解递归函数幕后可能发生的情况——几乎就像堆栈的每一层以及如何显示它一样。我改进了它以传递 level 参数,这样我就可以进行缩进,然后我有:

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

但即使这样也有点难以理解,因为它有太多多余的输入。添加各种调试助手以更好地可视化它可能是更好的方法吗?

根据脚本的冗长级别,以下内容似乎对潜在打印有用:

  1. 堆栈级别。这可以使用选项卡或类似的东西来完成,而不仅仅是“Level=1”。
  2. 是否达到基本情况。例如,上述函数在 1000 次递归函数调用中可能只达到基本情况 3 次。
  3. 应用函数的变量。

通过以上,我们可以将功能改进为如下形式:

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)

);

并且 non-verbose 调用将记录:

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

一种可能性是也跟踪 returns 并使用更多格式将事件组合在一个级别。

您可以在此处 运行,但您需要打开浏览器控制台才能查看结果,因为 Whosebug 的行数太多:

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}

它会给你这样的输出:

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

不算漂亮,但也不算太差。