JS递归代码抛出最大调用堆栈大小错误



我知道这可能是基本而简单的,但由于我在自学,所以我想知道哪里出了问题,我认为这是最好的学习场所。

我正在尝试编写一个返回true或false的递归代码。要检查的条件是,这组单词是否能构成给定的目标单词。

我经常得到的错误是:

if (targetString.indexOf(dictionary[i]) == 0) {
^
RangeError: Maximum call stack size exceeded
at String.indexOf (<anonymous>)

我很确定代码的问题在某种程度上是我正在返回的,因为我总是觉得它令人困惑。

我的代码是:

let targetString = "furniture";
let dictionary = ["fur", "ure", "nit"];
const tableData = {};
const canConstructRecursive = (targetString, dictionary) => {
if (targetString == "") {
return true
}
for (let i = 0; i < dictionary.length; i++) {
if (targetString.indexOf(dictionary[i]) == 0) {
shorterTargetString = targetString.slice(0, dictionary[i].length);
return canConstructRecursive(shorterTargetString, dictionary);
}
}
return false;
}
console.log(canConstructRecursive(targetString, dictionary));

我正在学习递归,有时我觉得我不理解返回下一个/上一个递归调用的逻辑。

如果有人能帮助我改正错误,改变我的思维方式,我将不胜感激。

我的想法是:

如果在该阶段达到基本情况,则返回基本情况,否则循环将遍历所有选项,内部节点或上层堆栈需要将值返回到下层堆栈,因此我在内部为返回canConstructRecursive()。如果即使在所有选项中都是for循环的所有迭代,它也没有返回,那么最后会返回false。

提前感谢

原因是尽管您的变量名为shorterTargetString,但不能保证它真的更短。如果idictionary中最短单词的索引,那么你的字符串不可能通过递归来变短

错误在于,切片不应在0处开始,而应在匹配的部分之后开始,因此从slice调用中删除第一个参数。

这将解决堆栈溢出错误。

其次,如果递归调用返回false,您不应该放弃,而是继续尝试下一个单词。因此,当您从递归中获得true时,只有return退出循环:

let targetString = "furniture";
let dictionary = ["fur", "ure", "nit"];
const tableData = {};
const canConstructRecursive = (targetString, dictionary) => {
if (targetString == "") {
return true
}
for (let i = 0; i < dictionary.length; i++) {
if (targetString.indexOf(dictionary[i]) == 0) {
shorterTargetString = targetString.slice(dictionary[i].length);
if (canConstructRecursive(shorterTargetString, dictionary)) {
return true;
};
}
}
return false;
}
console.log(canConstructRecursive(targetString, dictionary));

关于第二个修复的更多信息

您的代码将无条件地返回递归调用的值,即使它是false。这是不好的:如果递归调用返回false,那么调用者应该继续其for循环来尝试其他方法。

例如,让我们将一个单词添加到您的示例词典中:您会同意添加词典单词不应改变输入的结果";家具";。现在是:

["furn", "fur", "ure", "nit"]

但令人惊讶的是:您的代码现在返回CCD_;家具"!这是因为";furn";mathes的递归调用;iture;因为第一个参数找不到进一步的匹配,所以它返回false,现在调用者返回false。这是错误的。它应该放弃";furn";,但不是总的来说。它应该继续并尝试";毛皮";。这就是为什么退出for循环应该只在成功时发生,而不是在失败后发生。只有当所有字典单词都已尝试时,才能确认失败,因此只要没有递归成功,for循环就必须继续。

用户trincot已经解释了代码的错误。在这里,我只想指出,您的结构类似于for (...) {if (...) { if (...) {return true} } } return false,使用Array.prototype.some&&语句可能会更好地处理。再加上t .indexOf (s) == 0可能更清楚地表示为t .startsWith (s),并用条件语句而不是if语句,我们可以得出我认为更优雅的公式:

const canConstruct = (t = '', ss = []) => 
t == ''
? true
: ss .some ((s) => t .startsWith (s) && canConstruct (t .slice (s .length), ss))
console .log (canConstruct ('furniture', ['fur', 'ure', 'nit'])) //=> true
console .log (canConstruct ('furniture', ['furn', 'fur', 'ure', 'nit'])) //=> true
console .log (canConstruct ('banana', ['b', 'ana'])) //=> false
console .log (canConstruct ('banana', ['ba', 'na'])) //=> true

最新更新