繁体   English   中英

如何在 for 循环中正确调用递归 function?

[英]How to properly call a recursive function inside a for loop?

我正在尝试实现一个以参数为参数的方法:目标string和一个包含string值的array 目标是检查是否可以使用数组的值构造给定的目标字符串。数组中的单词可以根据需要多次使用。 例子:

console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"])); // suppose to return true

正如我们所看到的,通过连接"abc""def"我们得到了"abcdef"的目标字符串这是我的 function 实现:

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 行是此递归 function 的基本情况,然后通过遍历数组检查它是否以数组元素开头,如果为真则删除该特定子数组并使用新目标字符串和旧数组再次调用 function,如果为假则保留遍历整个 function 直到它到达基本情况。 所以再次使用前面的例子:

console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"]));  // return false

我错了,通过调试我可以看到它没有从第一次递归调用以来迭代整个数组。 我得到以下 output:

abcdef
cdef
ef
false

在我看来,这里有一个悬而未决的问题。 我们可以多次使用子字符串还是每次只使用一次? 也就是说,应该

canConstruct ("abcabc", ["ab", "abc", "cd", "def", "abcd"])

返回true因为我们可以将其分解为abc - abc ,使用第二个条目两次?

还是应该返回false因为我们一开始就已经用完了我们的abc

这两个片段是您的技术的变体,具有不同的风格。

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

 const canConstruct = (word, words) => word.length == 0? true: words.some ( (w) => word.startsWith (w) && canConstruct (word.replace (w, ''), words) ) console.log (canConstruct ("abcdef", ["ab", "abc", "cd", "def", "abcd"])) //=> true console.log (canConstruct ("abcddc", ["ab", "abc", "cd", "def", "abcd"])) //=> false console.log (canConstruct ("abcabc", ["ab", "abc", "cd", "def", "abcd"])) //=> true

第二个说我们只能使用一次:

 const removeFirst = (x, xs, i = xs.indexOf(x)) => i < 0? [... xs]: [... xs.slice (0, i), ... xs.slice (i + 1)] const canConstruct = (word, words) => word.length == 0? true: words.some ( (w) => word.startsWith (w) && canConstruct (word.replace (w, ''), removeFirst(w, words)) ) console.log (canConstruct ("abcdef", ["ab", "abc", "cd", "def", "abcd"])) //=> true console.log (canConstruct ("abcddc", ["ab", "abc", "cd", "def", "abcd"])) //=> false console.log (canConstruct ("abcabc", ["ab", "abc", "cd", "def", "abcd"])) //=> false console.log (canConstruct ("abcabc", ["ab", "abc", "cd", "abc", "def", "abcd"])) //=> true console.log (canConstruct ("abcabcabc", ["ab", "abc", "cd", "abc", "def", "abcd"])) //=> false

这里我们使用一个帮助器 function, removeFirst ,它在删除给定值的第一个实例后返回一个数组的副本。

即使您return false并以这种方式跳过所有其他组合,您也会打破 for 循环。 因此,就您而言,您只建立了一条道路

ab
cd

 const canConstruct = function (target, wordBank) { if (target === "") return true; for (let word of wordBank) { if (target.startsWith(word)) { if (canConstruct(target.replace(word, ""), wordBank))//break it only if true return true; } } return false; }; console.log("abcdef", canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"])); console.log("abc1def", canConstruct("abc1def", ["ab", "abc", "cd", "def", "abcd"]));

解决问题的答案已经提供并被接受。

尽管如此,在我处理数据模式/结构的难题之前,我都会尝试首先对此类数据进行清理/优化。

看看 OP 的术语...... "abcdef" ......和音节列表...... ["ab", "abc", "cd", "def", "abcd"] ......后者的项目可以在将其提供给 OP 的递归方法之前进行优化。

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

我想提供这样一种优化方法作为前期阶段,除了已经导致 OP 问题的方法......

 function orderSubstringsMostMatchingLtrByBoundConfig(a, b) { // order most matching from left to right. const { term, discard } = this; const aIdx = term.indexOf(a); // (>= 0) or (-1) const bIdx = term.indexOf(b); if (discard) { if ((aIdx === -1) &&.discard.has(a)) { // `a` is not a substring of `term`. discard;add(a). } if ((bIdx === -1) &&.discard.has(b)) { // `b` is not a substring of `term`; discard.add(b). } } return (aIdx - bIdx) || (b;length - a,length), } function sanitizeSyllablesList(list: term) { const config = { term; discard; new Set() }. // do not mutate the `list` argument. create a shallow copy. list = [...list ];sort( orderSubstringsMostMatchingLtrByBoundConfig.bind(config) ). return list.slice(config;discard,size), } const syllableList = ['cd', 'foofoo', 'abcd', 'def', 'bar', 'abc'; 'bazbizbuz'; 'ab']. const term = "abcdef": console,log( `term: '${ term }', sanitized syllables list,`; sanitizeSyllablesList(syllableList. term) ): console,log('original syllables list;', syllableList);
 .as-console-wrapper { min-height: 100%;important: top; 0; }

或者我们可以通过避免突变和变量 function 上下文来使 function 更加直接......

 const sanitizeSyllables = (list, term) => list.map(syllable => ({ syllable, pos: term.indexOf(syllable) })).filter(s => s.pos >= 0).sort((a, b) => a.pos - b.pos).map(s => s.syllable) const syllables = ['cd', 'foofoo', 'abcd', 'def', 'bar', 'abc', 'bazbizbuz', 'ab']; const term = "abcdef"; console.log( `term: '${ term }', sanitized syllables list:`, sanitizeSyllables(syllables, term) ); console.log('original syllables list:', syllables);
 .as-console-wrapper { min-height: 100%;important: top; 0; }

组合电子学

如果你有像combinationspermutations这样的通用函数,你可以很容易地解决这个问题 -

function canConstruct (target = "", parts = [])
{ for (c of combinations(parts))
    for (p of permutations(c))
      if (p.join("") == target)
        return true
  return false
}

其中combinations定义为 -

function* combinations (t)
{ if (t.length == 0)
    yield t
  else
    for (const c of combinations(t.slice(1)))
      ( yield c
      , yield [t[0], ...c]
      )
}

permutations被定义为 -

function permutations (t)
{ if (t.length < 2)
    return [t]
  else
    return permutations(t.slice(1))
      .flatMap(p => rotations(p, t[0]))
}

function rotations (t, e)
{ if (t.length == 0)
    return [[e]]
  else
    return [[e,...t], ...rotations(t.slice(1), e).map(r => [t[0], ...r])]
}

让我们测试一下 -

console.log(canConstruct("abcdef",    ["ab", "abc", "cd", "def", "abcd"]))
console.log(canConstruct("abcddc",    ["ab", "abc", "cd", "def", "abcd"]))
console.log(canConstruct("abcabc",    ["ab", "abc", "cd", "def", "abcd"]))
console.log(canConstruct("abcabc",    ["ab", "abc", "cd", "abc", "def", "abcd"]))
console.log(canConstruct("abcabcabc", ["ab", "abc", "cd", "abc", "def", "abcd"]))

展开下面的代码片段以在您自己的浏览器中验证结果 -

 function* combinations (t) { if (t.length == 0) yield t else for (const c of combinations(t.slice(1))) ( yield c, yield [t[0], ...c] ) } function permutations (t) { if (t.length < 2) return [t] else return permutations(t.slice(1)).flatMap(p => rotations(p, t[0])) } function rotations (t, e) { if (t.length == 0) return [[e]] else return [[e,...t], ...rotations(t.slice(1), e).map(r => [t[0], ...r])] } function canConstruct (target = "", parts = []) { for (c of combinations(parts)) for (p of permutations(c)) if (p.join("") == target) return true return false } console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"])) console.log(canConstruct("abcddc", ["ab", "abc", "cd", "def", "abcd"])) console.log(canConstruct("abcabc", ["ab", "abc", "cd", "def", "abcd"])) console.log(canConstruct("abcabc", ["ab", "abc", "cd", "abc", "def", "abcd"])) console.log(canConstruct("abcabcabc", ["ab", "abc", "cd", "abc", "def", "abcd"]))

true
false
false
true
false

斯科特的精彩答案有一个改编,允许多次重复使用给定的部分,即canConstruct("abab", ["ab"])true 我看不到这个答案的变体可以轻松实现这种行为。


短路

上面我们使用了一个简单的permutations实现,但是如果我们使用生成器,就像我们对combinations所做的那样,我们可以为我们的程序实现理想的短路行为 -

function* permutations (t)
{ if (t.length < 2)
    yield t
  else
    for (const p of permutations(t.slice(1))) // <- lazy
      for (const r of rotations(p, t[0]))     // <- lazy
        yield r
}

function* rotations (t, e)
{ if (t.length == 0)
    yield [e]
  else
    yield *chain
      ( [[e, ...t]]
      , map(rotations(t.slice(1), e), r => [t[0], ...r])
      )
}

这取决于使用生成器的两个通用函数mapchain -

function* map (t, f)
{ for (const e of t)
    yield f(e)
}

function* chain (...ts)
{ for (const t of ts)
    for (const e of t)
      yield e
}

canConstruct的实现不需要任何更改。 permutations将一一生成排列,当遇到return时,不会计算额外的排列 -

function canConstruct (target = "", parts = [])
{ for (c of combinations(parts))
    for (p of permutations(c))
      if (p.join("") == target)
        return true
  return false
}

展开下面的代码片段以在您自己的浏览器中验证结果 -

console.log(canConstruct("abcdef",    ["ab", "abc", "cd", "def", "abcd"]))
console.log(canConstruct("abcddc",    ["ab", "abc", "cd", "def", "abcd"]))
console.log(canConstruct("abcabc",    ["ab", "abc", "cd", "def", "abcd"]))
console.log(canConstruct("abcabc",    ["ab", "abc", "cd", "abc", "def", "abcd"]))
console.log(canConstruct("abcabcabc", ["ab", "abc", "cd", "abc", "def", "abcd"]))

 function* combinations (t) { if (t.length == 0) yield t else for (const c of combinations(t.slice(1))) ( yield c, yield [t[0], ...c] ) } function* permutations (t) { if (t.length < 2) yield t else for (const p of permutations(t.slice(1))) for (const r of rotations(p, t[0])) yield r } function* rotations (t, e) { if (t.length == 0) yield [e] else yield *chain ( [[e, ...t]], map(rotations(t.slice(1), e), r => [t[0], ...r]) ) } function* map (t, f) { for (const e of t) yield f(e) } function* chain (...ts) { for (const t of ts) for (const e of t) yield e } function canConstruct (target = "", parts = []) { for (c of combinations(parts)) for (p of permutations(c)) if (p.join("") == target) return true return false } console.log(canConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"])) console.log(canConstruct("abcddc", ["ab", "abc", "cd", "def", "abcd"])) console.log(canConstruct("abcabc", ["ab", "abc", "cd", "def", "abcd"])) console.log(canConstruct("abcabc", ["ab", "abc", "cd", "abc", "def", "abcd"])) console.log(canConstruct("abcabcabc", ["ab", "abc", "cd", "abc", "def", "abcd"]))

true
false
false
true
false

有关使用生成器解决此问题的其他说明和其他好处,请参阅此相关问答

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM