diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index ef8dc8b3ec6..ca1a232ce04 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -4934,6 +4934,13 @@ "In this lab, you will build a function that finds the missing letter in a given range of consecutive letters and returns it." ] }, + "lab-proofreading-tool": { + "title": "Build a Proofreading Tool", + "intro": [ + "In this lab, you will build a proofreading tool that analyzes arrays of words for palindromes and repeated phrases.", + "You will practice for loops and nested loops to check palindromes and find repeated word sequences." + ] + }, "review-javascript-loops": { "title": "JavaScript Loops Review", "intro": [ diff --git a/curriculum/challenges/english/blocks/lab-proofreading-tool/69dd63d1dcdeccb7b39ba4c3.md b/curriculum/challenges/english/blocks/lab-proofreading-tool/69dd63d1dcdeccb7b39ba4c3.md new file mode 100644 index 00000000000..3265bec2c53 --- /dev/null +++ b/curriculum/challenges/english/blocks/lab-proofreading-tool/69dd63d1dcdeccb7b39ba4c3.md @@ -0,0 +1,207 @@ +--- +id: 69dd63d1dcdeccb7b39ba4c3 +title: Build a Proofreading Tool +challengeType: 26 +dashedName: build-a-proofreading-tool +--- + +# --description-- + +In this lab, you will build a proofreading tool that analyzes arrays of words for palindromes and repeated phrases. + +A palindrome is a word that reads the same forwards and backwards. For example, `"racecar"` and `"level"` are palindromes, but `"hello"` is not. + +A phrase is a sequence of consecutive words. For example, in `["the", "cat", "sat", "the", "cat"]`, the phrase `"the cat"` (a sequence of 2 words) appears at positions 0 and 3. + +**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. + +**User Stories:** + +1. You should define a function named `isPalindrome` that takes a `word` string as its argument. It should return `true` if the word reads the same forwards and backwards (case-insensitive), and `false` otherwise. + +2. You should define a function named `findPalindromeBreaks` that takes a `words` array as its argument. It should return an array of indices of words that are not palindromes. It should return an empty array if the input is empty. + +3. You should define a function named `findRepeatedPhrases` that takes a `words` array and a `phraseLength` number as arguments. It should return an array of all start indices where a sequence of `phraseLength` consecutive words appears more than once in the array — including the index of the first occurrence. It should return an empty array if `phraseLength` is greater than or equal to the length of `words`. + +4. You should define a function named `analyzeTexts` that takes a `texts` array and a `phraseLength` number as arguments. It should process each element of `texts` (each an array of words) and return an array of objects, each with `repeatedPhrases` and `palindromeBreaks` properties. It should return an empty array if `texts` is empty. + +# --hints-- + +`isPalindrome` should be a function. + +```js +assert.isFunction(isPalindrome); +``` + +`isPalindrome` should return `true` for a palindrome. + +```js +assert.isTrue(isPalindrome("racecar")); +assert.isTrue(isPalindrome("a")); +``` + +`isPalindrome` should return `true` regardless of case. + +```js +assert.isTrue(isPalindrome("Level")); +``` + +`isPalindrome` should return `false` for a non-palindrome. + +```js +assert.isFalse(isPalindrome("hello")); +``` + +`findPalindromeBreaks` should be a function. + +```js +assert.isFunction(findPalindromeBreaks); +``` + +`findPalindromeBreaks` should return an empty array for empty input. + +```js +assert.sameDeepOrderedMembers(findPalindromeBreaks([]), []); +``` + +`findPalindromeBreaks` should return the indices of non-palindromes. + +```js +assert.sameDeepOrderedMembers(findPalindromeBreaks(["racecar", "hello", "level"]), [1]); +``` + +`findPalindromeBreaks` should return an empty array when all words are palindromes. + +```js +assert.sameDeepOrderedMembers(findPalindromeBreaks(["racecar", "level", "aba"]), []); +``` + +`findRepeatedPhrases` should be a function. + +```js +assert.isFunction(findRepeatedPhrases); +``` + +`findRepeatedPhrases` should return an empty array when `phraseLength` is greater than or equal to the length of `words`. + +```js +assert.sameDeepOrderedMembers(findRepeatedPhrases(["the", "cat"], 2), []); +``` + +`findRepeatedPhrases` should return an empty array when `phraseLength` is greater than the length of `words`. + +```js +assert.sameDeepOrderedMembers(findRepeatedPhrases(["the"], 2), []); +``` + +`findRepeatedPhrases` should return all start indices where the phrase repeats, including the first occurrence. + +```js +assert.sameDeepOrderedMembers(findRepeatedPhrases(["the", "cat", "sat", "the", "cat"], 2), [0, 3]); +``` + +`analyzeTexts` should be a function. + +```js +assert.isFunction(analyzeTexts); +``` + +`analyzeTexts` should return an empty array for empty input. + +```js +assert.sameDeepOrderedMembers(analyzeTexts([], 2), []); +``` + +`analyzeTexts` result objects should have `repeatedPhrases` and `palindromeBreaks` properties. + +```js +const result = analyzeTexts([["racecar", "hello"]], 2); +assert.property(result[0], "repeatedPhrases"); +assert.property(result[0], "palindromeBreaks"); +``` + +`analyzeTexts` should correctly aggregate results for each text. + +```js +const result = analyzeTexts([["racecar", "hello", "level", "hello"]], 1); +assert.sameDeepOrderedMembers(result[0].repeatedPhrases, [1, 3]); +assert.sameDeepOrderedMembers(result[0].palindromeBreaks, [1, 3]); +``` + +`analyzeTexts` should process multiple texts and return a result for each. + +```js +const result = analyzeTexts([["racecar", "hello"], ["level", "world", "level"]], 1); +assert.lengthOf(result, 2); +assert.sameDeepOrderedMembers(result[1].palindromeBreaks, [1]); +assert.sameDeepOrderedMembers(result[1].repeatedPhrases, [0, 2]); +``` + +# --seed-- + +## --seed-contents-- + +```js + +``` + +# --solutions-- + +```js +function isPalindrome(word) { + const lower = word.toLowerCase(); + for (let i = 0; i < Math.floor(lower.length / 2); i++) { + if (lower[i] !== lower[lower.length - 1 - i]) { + return false; + } + } + return true; +} + +function findPalindromeBreaks(words) { + const breaks = []; + if (words.length === 0) return breaks; + for (let i = 0; i < words.length; i++) { + if (!isPalindrome(words[i])) { + breaks.push(i); + } + } + return breaks; +} + +function findRepeatedPhrases(words, phraseLength) { + const result = []; + if (phraseLength >= words.length) return result; + + for (let i = 0; i <= words.length - phraseLength; i++) { + const phrase = words.slice(i, i + phraseLength).join(" "); + let found = false; + + for (let j = 0; j <= words.length - phraseLength; j++) { + if (i === j) continue; + if (words.slice(j, j + phraseLength).join(" ") === phrase) { + found = true; + break; + } + } + + if (found) result.push(i); + } + + return result; +} + +function analyzeTexts(texts, phraseLength) { + const results = []; + if (texts.length === 0) return results; + + for (let i = 0; i < texts.length; i++) { + results.push({ + repeatedPhrases: findRepeatedPhrases(texts[i], phraseLength), + palindromeBreaks: findPalindromeBreaks(texts[i]) + }); + } + + return results; +} +``` diff --git a/curriculum/structure/blocks/lab-proofreading-tool.json b/curriculum/structure/blocks/lab-proofreading-tool.json new file mode 100644 index 00000000000..dbc7e5fcc8b --- /dev/null +++ b/curriculum/structure/blocks/lab-proofreading-tool.json @@ -0,0 +1,14 @@ +{ + "blockLabel": "lab", + "blockLayout": "link", + "isUpcomingChange": false, + "usesMultifileEditor": true, + "dashedName": "lab-proofreading-tool", + "helpCategory": "JavaScript", + "challengeOrder": [ + { + "id": "69dd63d1dcdeccb7b39ba4c3", + "title": "Build a Proofreading Tool" + } + ] +} diff --git a/curriculum/structure/superblocks/javascript-v9.json b/curriculum/structure/superblocks/javascript-v9.json index 972646ff3d7..e54ee867ce0 100644 --- a/curriculum/structure/superblocks/javascript-v9.json +++ b/curriculum/structure/superblocks/javascript-v9.json @@ -109,6 +109,7 @@ "lab-repeat-a-string", "workshop-festival-crowd-flow-simulator", "lab-missing-letter-detector", + "lab-proofreading-tool", "review-javascript-loops", "quiz-javascript-loops" ]