[英]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; }
組合電子學
如果你有像combinations
和permutations
這樣的通用函數,你可以很容易地解決這個問題 -
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])
)
}
這取決於使用生成器的兩個通用函數map
和chain
-
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.