簡體   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