Javascript 如何在for循环中正确调用递归函数?

Javascript 如何在for循环中正确调用递归函数?,javascript,recursion,Javascript,Recursion,我正在尝试实现一个方法,该方法将作为参数:targetstring和一个array,其中包含string值。我们的目标是检查是否可以使用数组的值构造给定的目标字符串。数组中的单词可以使用任意次数。例如: console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"])); // suppose to r

我正在尝试实现一个方法,该方法将作为参数:target
string
和一个
array
,其中包含
string
值。我们的目标是检查是否可以使用数组的值构造给定的目标字符串。数组中的单词可以使用任意次数。例如:

console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"])); // suppose to return true
正如我们所看到的,通过连接
“abc”
“def”
我们得到了
“abcdef”
下面是我的函数实现:

const canConstruct = function (target, wordBank) {
  if (target === "") return true;
  console.log(target);
  for (let word of wordBank) {
    if (target.startsWith(word)) {
      return canConstruct(target.replace(word, ""), wordBank);
    }
  }

  return false;
};  
第2行是这个递归函数的基本情况,然后通过迭代数组检查它是否从数组元素开始,如果为true,则删除该特定子数组,并使用新的目标字符串和旧数组再次调用该函数,如果为false,则继续迭代整个函数,直到达到基本情况。再次使用前面的示例:

console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"]));  // return false
我得到了false,通过调试,我可以看到它从第一次递归调用开始就没有迭代整个数组。我得到以下输出:

abcdef
cdef
ef
false

即使
返回false
并以这种方式跳过所有其他组合,您也在中断for循环。因此,在您的案例中,您只创建了一条路径

ab
cd
const canConstruct=函数(目标,字库){
如果(目标==“”)
返回true;
for(让wordBank的单词){
if(target.startsWith(word)){
if(canConstruct(target.replace(word,“”,wordBank))//仅当为true时才断开它
返回true;
}
}
返回false;
};
log(“abcdef”,canConstruct(“abcdef”,“ab”,“abc”,“cd”,“def”,“abcd”));

log(“abc1def”,canConstruct(“abc1def”,“ab”,“abc”,“cd”,“def”,“abc”))问题解决答案已提供并被接受

然而,在处理数据模式/结构的谜题之前,我会首先尝试清理/优化这些数据

看看OP的术语<代码>“abcdef”
。。。还有音节表<代码>[“ab”、“abc”、“cd”、“def”、“abcd”]。。。后者的项目顺序可以在将其输入OP的递归方法之前进行优化

此外,如果您认为列表中的音节不会被使用,因为它们都不是要构造的术语/单词/表达式的子字符串,那么您可能希望在开始递归过程之前从音节列表中丢弃这些项目

我想提供这样一种优化方法作为前期工作,除了已经导致OP问题的因素之外

函数ordersubstringsMatchingLTRByboundConfig(a,b){
//从左到右排列最匹配的。
const{term,discard}=这个;
常数aIdx=项索引(a);//(>=0)或(-1)
常数bIdx=术语索引f(b);
如果(放弃){
如果((aIdx==-1)&&!discard.has(a)){
//'a'不是'term'的子字符串。
丢弃。添加(a);
}
如果((bIdx==-1)&&!放弃.has(b)){
//'b'不是'term'的子字符串。
丢弃。添加(b);
}
}
返回(aIdx-bIdx)| |(b.length-a.length);
}
函数sanitizeSyllablesList(列表,术语){
const config={term,discard:new Set()};
//不要改变'list'参数;创建一个浅拷贝。
列表=[…列表]。排序(
OrderSubstringsMatchingLTRByboundConfig.bind(配置)
);
return list.slice(config.discard.size);
}
常量音节列表=['cd'、'foofoo'、'abcd'、'def'、'bar'、'abc'、'bazbizbuz'、'ab'];
const term=“abcdef”;
console.log(
`术语:“${term}”,净化音节列表:`,
健全音节列表(音节列表,术语)
);
log(“原始音节列表:”,音节列表)

.as控制台包装{min height:100%!important;top:0;}
在我看来,这里有一个悬而未决的问题。我们可以多次使用子字符串,还是每次只使用一次?就是应该

canConstruct(“abcabc”、[“ab”、“abc”、“cd”、“def”、“abcd”])
返回
true
,因为我们可以使用第二个条目两次将其分解为
abc
-
abc

或者它应该返回
false
,因为我们一开始就已经用完了
abc

这两个代码段是不同风格的技术变体

第一种假设我们可以根据需要经常使用子字符串:

const canConstruct=(单词,单词)=>
word.length==0
? 真的
:一些(
(w) =>word.startsWith(w)和&canConstruct(word.replace(w),),words)
)
log(canConstruct(“abcdef”,“ab”,“abc”,“cd”,“def”,“abcd”]))/=>true
log(canConstruct(“abcddc”,“ab”,“abc”,“cd”,“def”,“abcd”]))/=>false

console.log(canConstruct(“abcabc”,“ab”,“abc”,“cd”,“def”,“abc”]))/=>true
组合电子技术

如果您有像
组合
置换
这样的通用函数,您可以轻松解决此问题-

函数canConstruct(target=”“,parts=[])
{对于(组合的c部分))
对于(置换的p(c))
if(p.join(“”==目标)
返回真值
返回错误
}
其中,
组合
定义为-

函数*组合(t)
{if(t.length==0)
产量t
其他的
对于(组合的常数c(t.slice(1)))
(c)收益率
,收益率[t[0],…c]
)
}
置换
定义为-

函数置换(t)
{if(t.length<2)
返回[t]
其他的
返回置换(t.slice(1))
.flatMap(p=>旋转(p,t[0]))
}
函数旋转(t,e)
{if(t.length==0)
返回[[e]]
其他的
返回[[e,…t],…旋转(t.slice(1),e).map(r=>[t[0],…r])]
}
让我们测试一下-

console.log(canConstruct(“abcdef”,“ab”,“abc”,“cd”,“def”,“abcd”]))
日志(canConstruct(“abcddc”,“ab”,“abc”,“cd”,“def”,“abcd”]))
控制台