2 回答

TA貢獻(xiàn)2039條經(jīng)驗 獲得超8個贊
一系列的重構(gòu)
經(jīng)過一系列的重構(gòu),我們最終將得到以下代碼:
const isPalindrome = (s) =>
s.length == 0 ? true : s[0] === s[s.length - 1] && isPalindrome(s.slice(1, -1))
const countPalindromicPrefixes = (s) =>
s.length < 2 ? 0 : (isPalindrome(s) ? 1 : 0) + countPalindromicPrefixes(s.slice (0, -1))
const countPalindromes = (s) =>
s.length < 2 ? 0 : countPalindromicPrefixes(s) + countPalindromes(s.slice(1))
console .log (countPalindromes ("kayak"));
console .log (countPalindromes ("aya"));
console .log (countPalindromes ("appal"));
console .log (countPalindromes ("addadaadd"));
console .log (countPalindromes ("madamimadam"));
每個步驟都有一個片段(默認(rèn)情況下隱藏,因此不會太長),顯示到目前為止我們還沒有破壞任何內(nèi)容。
初始點
初始代碼如下所示:
function returnNumberOfPalindromes(word) {
function checkForPalindrome(chunk) {
let chunkArray = [];
for (const letter of chunk) {
chunkArray.push(letter);
}
if (chunkArray.reverse().join('') === chunk) {
tally += 1;
}
}
let tally = 0;
for (let index1 = 0, index2 = 2; index1 <= word.length; index1++, (index2 = index1 + 2)) {
for (; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
checkForPalindrome(chunk);
}
}
console.log(tally);
}
function returnNumberOfPalindromes(word) {
function checkForPalindrome(chunk) {
let chunkArray = [];
for (const letter of chunk) {
chunkArray.push(letter);
}
if (chunkArray.reverse().join('') === chunk) {
tally += 1;
}
}
let tally = 0;
for (let index1 = 0, index2 = 2; index1 <= word.length; index1++, (index2 = index1 + 2)) {
for (; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
checkForPalindrome(chunk);
}
}
console.log(tally);
}
returnNumberOfPalindromes("kayak");
returnNumberOfPalindromes("aya");
returnNumberOfPalindromes("appal");
returnNumberOfPalindromes("addadaadd");
修復(fù)for循環(huán)
作為外部循環(huán)增量的一部分更新內(nèi)部循環(huán)的初始索引有一些非常奇怪的事情。這是更常見的,而且我敢說,更符合邏輯:
for (let index1 = 0; index1 <= word.length; index1++) {
for (let index2 = index1 + 2; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
checkForPalindrome(chunk);
}
}
function returnNumberOfPalindromes(word) {
function checkForPalindrome (chunk) {
if (chunk.split('').reverse().join('') === chunk) {
tally += 1;
}
}
let tally = 0;
for (let index1 = 0; index1 <= word.length; index1++) {
for (let index2 = index1 + 2; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
checkForPalindrome(chunk);
}
}
console.log(tally);
}
returnNumberOfPalindromes("kayak");
returnNumberOfPalindromes("aya");
returnNumberOfPalindromes("appal");
returnNumberOfPalindromes("addadaadd");
returnNumberOfPalindromes("madamimadam");
簡化checkForPalindrome
正在進(jìn)行的大部分工作checkForPalindrome都是不必要的。 String.prototype.split已經(jīng)將字符串轉(zhuǎn)換為字符數(shù)組。所以我們可以將該部分更改為:
function checkForPalindrome (chunk) {
if (chunk.split('').reverse().join('') === chunk) {
tally += 1;
}
}
function returnNumberOfPalindromes(word) {
function checkForPalindrome (chunk) {
if (chunk.split('').reverse().join('') === chunk) {
tally += 1;
}
}
let tally = 0;
for (let index1 = 0; index1 <= word.length; index1++) {
for (let index2 = index1 + 2; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
checkForPalindrome(chunk);
}
}
console.log(tally);
}
returnNumberOfPalindromes("kayak");
returnNumberOfPalindromes("aya");
returnNumberOfPalindromes("appal");
returnNumberOfPalindromes("addadaadd");
returnNumberOfPalindromes("madamimadam");
使局部函數(shù)成為純函數(shù)
我們的內(nèi)部函數(shù)checkForPalindrome沒有返回值。相反,它正在更新 的狀態(tài)tally。這使得它更難理解。讓我們將其更改為checkForPalindromereturntrue或false,并tally根據(jù)該結(jié)果進(jìn)行更新:
function checkForPalindrome (chunk) {
return chunk.split('').reverse().join('') === chunk
}
if (checkForPalindrome(chunk)) {
tally += 1
}
function returnNumberOfPalindromes(word) {
function checkForPalindrome (chunk) {
return chunk.split('').reverse().join('') === chunk
}
let tally = 0;
for (let index1 = 0; index1 <= word.length; index1++) {
for (let index2 = index1 + 2; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
if (checkForPalindrome(chunk)) {
tally += 1
}
}
}
console.log(tally);
}
returnNumberOfPalindromes("kayak");
returnNumberOfPalindromes("aya");
returnNumberOfPalindromes("appal");
returnNumberOfPalindromes("addadaadd");
returnNumberOfPalindromes("madamimadam");
取出內(nèi)部函數(shù)
仍然關(guān)注checkForPalindrome,我們可以注意到它現(xiàn)在是一個有用的函數(shù)來確定字符串是否是回文。因此,我們不妨將其從我們的主要功能中取出并使其獨(dú)立。
true但返回or的函數(shù)的常見約定false是將其命名為以is. 在這種情況下,isPalindrome肯定更有意義。所以我們改成這樣:
function isPalindrome (word) {
return word.split('').reverse().join('') === word
}
和
if (isPalindrome(chunk)) {
tally += 1
}
function isPalindrome (word) {
return word.split('').reverse().join('') === word
}
function returnNumberOfPalindromes(word) {
let tally = 0;
for (let index1 = 0; index1 <= word.length; index1++) {
for (let index2 = index1 + 2; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
if (isPalindrome(chunk)) {
tally += 1
}
}
}
return tally;
}
console .log (returnNumberOfPalindromes("addadaadd")); //=> 7
把這個遞歸
現(xiàn)在邁出一大步。我們想讓這個遞歸。讓我們檢查一下主循環(huán)正在做什么:
for (let index1 = 0; index1 <= word.length; index1++) {
for (let index2 = index1 + 2; index2 <= word.length; index2++) {
let chunk = word.slice(index1, index2);
if (isPalindrome(chunk)) {
tally += 1
}
}
}
此代碼在索引中循環(huán)兩次,查找從索引 0 開始的所有子字符串,然后從索引 1 開始查找所有子字符串,然后從索引 2 開始查找所有子字符串,依此類推。對于每個子字符串,它都會測試字符串是否為回文,以及是否為回文。 ,我們增加計數(shù)。
這種雙循環(huán)幾乎肯定意味著我們也會進(jìn)行雙遞歸。所以我們可以認(rèn)為它有兩個功能。
一個函數(shù)接受一個字符串并查看該字符串的所有前綴(例如,對于“goat”,前綴將為“goat”、“goa”、“go”和“g”)并計算那些前綴的數(shù)量。是回文。如果單詞長度少于兩個字符,則它沒有回文,我們返回0,否則我們包含1該單詞是否是回文,0如果不是,則將其添加到刪除最后一個字符的遞歸調(diào)用的結(jié)果中:
function returnNumberOfPalindromicPrefixes(word) {
if (word .length < 2) {
return 0
}
return (isPalindrome(word) ? 1 : 0) + returnNumberOfPalindromicPrefixes(word.slice(0, -1))
}
第二個函數(shù)在字符串的開頭重復(fù)出現(xiàn)。同樣,如果單詞長度少于兩個字符,我們再次返回0。否則,我們將調(diào)用前綴函數(shù)的結(jié)果添加到對刪除第一個字符形成的字符串的遞歸調(diào)用中:
function returnNumberOfPalindromes(word) {
if (word .length < 2) {
return 0
}
return returnNumberOfPalindromicPrefixes(word) + returnNumberOfPalindromes(word.slice(1))
}
function isPalindrome(word) {
return word.split('').reverse().join('') === word
}
function returnNumberOfPalindromicPrefixes(word) {
if (word .length < 2) {
return 0
}
return (isPalindrome(word) ? 1 : 0) + returnNumberOfPalindromicPrefixes(word.slice(0, -1))
}
function returnNumberOfPalindromes(word) {
if (word .length < 2) {
return 0
}
return returnNumberOfPalindromicPrefixes(word) + returnNumberOfPalindromes(word.slice(1))
}
console .log (returnNumberOfPalindromes("kayak"));
console .log (returnNumberOfPalindromes("aya"));
console .log (returnNumberOfPalindromes("appal"));
console .log (returnNumberOfPalindromes("addadaadd"));
console .log (returnNumberOfPalindromes("madamimadam"));
插曲
至此我們就有了合理的代碼。我們將其重構(gòu)為邏輯相當(dāng)清晰且相當(dāng)清晰的遞歸函數(shù)。(這也與 Applet123 的答案非常相似。)
這對您(或您的教練)來說可能已經(jīng)足夠了。
但我認(rèn)為還有更多有用的改變需要做出。將接下來的幾個步驟視為錦上添花。
函數(shù)命名
這聽起來可能微不足道,但名字returnNumberOf<Whatever>
卻相當(dāng)可怕。count<Whatever>
可能numberOf<Whatever>
會好得多sizeOf<Whatever>
。
我將選擇第一個,它將為我們提供名稱countPalindromicPrefixes
和countPalindromes
。
function isPalindrome (word) {
return word.split('').reverse().join('') === word
}
function countPalindromicPrefixes(word) {
if (word .length < 2) {
return 0
}
return (isPalindrome(word) ? 1 : 0) + countPalindromicPrefixes(word.slice(0, -1))
}
function countPalindromes(word) {
if (word .length < 2) {
return 0
}
return countPalindromicPrefixes(word) + countPalindromes(word.slice(1))
}
console .log (countPalindromes("kayak"));
console .log (countPalindromes("aya"));
console .log (countPalindromes("appal"));
console .log (countPalindromes("addadaadd"));
console .log (countPalindromes("madamimadam"));
更現(xiàn)代的 JS
使用箭頭函數(shù)而不是函數(shù)聲明可以清理大量代碼。 條件(三元)運(yùn)算符也可以。使用這些我們可以變成這樣:
function countPalindromes(word) {
if (word .length < 2) {
return 0
}
return countPalindromicPrefixes(word) + countPalindromes(word.slice(1))
}
進(jìn)入這個:
const countPalindromes = (word) =>
word .length < 2 ? 0 : countPalindromicPrefixes(word) + countPalindromes(word.slice(1))
并對其他功能執(zhí)行類似的操作。
const isPalindrome = (word) =>
word.split('').reverse().join('') === word
const countPalindromicPrefixes = (word) =>
word.length < 2 ? 0 : (isPalindrome(word) ? 1 : 0) + countPalindromicPrefixes(word.slice(0, -1))
const countPalindromes = (word) =>
word.length < 2 ? 0 : countPalindromicPrefixes(word) + countPalindromes(word.slice(1))
console .log (countPalindromes ("kayak"));
console .log (countPalindromes ("aya"));
console .log (countPalindromes ("appal"));
console .log (countPalindromes ("addadaadd"));
console .log (countPalindromes ("madamimadam"));
參數(shù)命名
我們在這里為我們的函數(shù)使用參數(shù)名稱“word”。但我們真的想讓這成為一個詞嗎?這句話“一個人,一個計劃,一條運(yùn)河:巴拿馬!” 被廣泛描述為回文。(雖然它不適合我們的測試,但我們可以通過在執(zhí)行當(dāng)前測試之前降低值并刪除任何非字母字符來輕松修復(fù)它。)事實上,我向其中添加了一個測試用例,它捕獲另一個經(jīng)典回文的版本“女士,我是亞當(dāng)”。
那么輸入可以是一個單詞或一個句子?也許也是一個短語?人們有時會談?wù)摂?shù)字是回文數(shù),例如 2112。因此“單詞”并不是這些數(shù)字的正確名稱。我們的似乎采用任意字符串。因此,我們應(yīng)該將參數(shù)重命名為捕獲該參數(shù)的名稱。也許是“str”?也許是“字符串”?我將提出一些更激進(jìn)的建議。我們確實不知道類型,而且它是一個單行函數(shù),因此仍然具有“字符串”風(fēng)格的單字符名稱在這里似乎完全合理。我會選擇“s”。請注意,有些人厭惡這種想法。這可能包括你的教練,所以要小心。(如果有人反對,你可以隨時向他們推薦一篇關(guān)于這個想法的文章,描述性變量名稱:代碼味道,約翰·德戈斯著。但他們可能仍然開始向你扔?xùn)|西,所以要小心。)
所以我們可以這樣寫:
const countPalindromicPrefixes = (s) =>
s.length < 2 ? 0 : (isPalindrome(s) ? 1 : 0) + countPalindromicPrefixes(s.slice(0, -1))
const isPalindrome = (s) =>
s.split('').reverse ().join('') === s
const countPalindromicPrefixes = (s) =>
s.length < 2 ? 0 : (isPalindrome(s) ? 1 : 0) + countPalindromicPrefixes(s.slice(0, -1))
const countPalindromes = (s) =>
s.length < 2 ? 0 : countPalindromicPrefixes(s) + countPalindromes(s .slice(1))
console .log (countPalindromes ("kayak"));
console .log (countPalindromes ("aya"));
console .log (countPalindromes ("appal"));
console .log (countPalindromes ("addadaadd"));
console .log (countPalindromes ("madamimadam"));
遞歸地做所有的事情
除了課堂作業(yè)之外,我不會做以下事情。我們已經(jīng)有了 的一個工作版本isPalindrome
,一個可讀且高效的版本。但由于這顯然是一個遞歸分配,因此編寫一個遞歸版本可能會很好isPalindrome
。
為此,我們可以遞歸地考慮這個問題,注意如果第一個和最后一個字符匹配并且中間的字符串也是回文,則字符串是回文。空字符串在這里被視為回文,單字符字符串也是如此。(在這種情況下,第一個和最后一個字符指向同一個地方,所以它們當(dāng)然是相同的。)我們可以這樣寫:
const isPalindrome = (s) => s.length == 0 ? true : s[0] === s[s.length - 1] && isPalindrome(s.slice(1, -1))
請注意,對于這里的一般問題,我們沒有將 1 字符子字符串視為回文,但這是在主函數(shù)中處理的。 isPalindrome
很高興地這樣報告它們,這似乎很合適。
const isPalindrome = (s) =>
s.length == 0 ? true : s[0] === s[s.length - 1] && isPalindrome(s.slice(1, -1))
const countPalindromicPrefixes = (s) =>
s.length < 2 ? 0 : (isPalindrome(s) ? 1 : 0) + countPalindromicPrefixes(s.slice (0, -1))
const countPalindromes = (s) =>
s.length < 2 ? 0 : countPalindromicPrefixes(s) + countPalindromes(s.slice(1))
console .log (countPalindromes ("kayak"));
console .log (countPalindromes ("aya"));
console .log (countPalindromes ("appal"));
console .log (countPalindromes ("addadaadd"));
console .log (countPalindromes ("madamimadam"));

TA貢獻(xiàn)1807條經(jīng)驗 獲得超9個贊
我不會checkForPalindrome遞歸地編寫,因為這樣做很簡單:
function checkForPalindrome(str) {
// we can use .split("") to convert to an array of characters
return str.split("").reverse().join("") == str;
}
function palindromesAtStart(str) {
// lengths 0 and 1 can't be palindromes
if (str.length < 2) {
return 0;
}
// if it's a palindrome add 1
const diff = checkForPalindrome(str) ? 1 : 0;
// now shorten the string and check again
return diff + palindromesAtStart(str.slice(0, -1));
}
function returnNumberOfPalindromes(str) {
if (str.length < 2) {
return 0;
}
// total palindromes is palindromes at start plus palindromes not at start
return palindromesAtStart(str) + returnNumberOfPalindromes(str.slice(1));
}
console.log(returnNumberOfPalindromes("kayak"));
console.log(returnNumberOfPalindromes("aya"));
console.log(returnNumberOfPalindromes("appal"));
console.log(returnNumberOfPalindromes("addadaadd"));
本質(zhì)上,字符串中的回文可以包含第一個索引,也可以不包含第一個索引。因此,我們可以編寫一個簡單的遞歸函數(shù)(palindromesAtStart)來計算包含第一個索引的回文數(shù),并將其添加到不包含第一個索引的回文數(shù)中。
添加回答
舉報