diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index c9e726eb83e..200e223eb65 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -4872,6 +4872,13 @@ "In this workshop, you'll leverage JavaScript loops to build a space mission roster." ] }, + "workshop-heritage-library-catalog": { + "title": "Build a Heritage Library Catalog", + "intro": [ + "In this workshop, you will digitize historical catalog cards for a heritage library.", + "You will practice using loops, objects, and string methods to parse raw text data, search and group entries, render formatted output, and export to JSON and CSV." + ] + }, "lab-longest-word-in-a-string": { "title": "Build a Longest Word Finder App", "intro": [ diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d1f8e51fcdc8230f779fb.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d1f8e51fcdc8230f779fb.md new file mode 100644 index 00000000000..230bdd624ab --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d1f8e51fcdc8230f779fb.md @@ -0,0 +1,55 @@ +--- +id: 699d1f8e51fcdc8230f779fb +title: Step 1 +challengeType: 1 +dashedName: step-1 +--- + +# --description-- + +In this workshop, you will build a heritage library catalog system. You will parse raw book data, search and group entries, validate data quality, and export results to different formats. + +Each catalog card is stored as a single string in the format `"Title | Author | Year | Location"`, with fields separated by the pipe character (`|`). + +You will use an array of these strings throughout the workshop to build and test your catalog functions. Create an array named `rawCatalogCards` and add the following two strings to it: + +```js +"From a Buick 8 | King, Stephen | 2002 | Shelf K7", +"The Shining | King, Stephen | 1977 | Shelf K1" +``` + +# --hints-- + +You should declare a variable named `rawCatalogCards`. + +```js +assert.match(__helpers.removeJSComments(code), /(const|let|var)\s+rawCatalogCards\s*=/); +``` + +`rawCatalogCards` should be an array. + +```js +assert.isArray(rawCatalogCards); +``` + +The first element of `rawCatalogCards` should be `"From a Buick 8 | King, Stephen | 2002 | Shelf K7"`. + +```js +assert.strictEqual(rawCatalogCards[0], "From a Buick 8 | King, Stephen | 2002 | Shelf K7"); +``` + +The second element of `rawCatalogCards` should be `"The Shining | King, Stephen | 1977 | Shelf K1"`. + +```js +assert.strictEqual(rawCatalogCards[1], "The Shining | King, Stephen | 1977 | Shelf K1"); +``` + +# --seed-- + +## --seed-contents-- + +```js +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885450.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885450.md new file mode 100644 index 00000000000..2a70f9b2845 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885450.md @@ -0,0 +1,60 @@ +--- +id: 699d20623e57e8ffee885450 +title: Step 2 +challengeType: 1 +dashedName: step-2 +--- + +# --description-- + +The rest of the catalog cards have been added. Notice the three entries near the end with missing fields; you will handle those in a later step. + +`parseCard` will take a raw catalog string and return a structured object with four properties: `title`, `author`, `year`, and `location`. For now, create a function `parseCard` with a parameter named `rawString`. + +# --hints-- + +You should have a function named `parseCard`. + +```js +assert.isFunction(parseCard); +``` + +`parseCard` should be a function with a parameter named `rawString`. + +```js +const regex = __helpers.functionRegex('parseCard', ['rawString']); +assert.match(__helpers.removeJSComments(code), regex); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885451.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885451.md new file mode 100644 index 00000000000..893399d3b6d --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885451.md @@ -0,0 +1,69 @@ +--- +id: 699d20623e57e8ffee885451 +title: Step 3 +challengeType: 1 +dashedName: step-3 +--- + +# --description-- + +The `parseCard` function will take a raw catalog string and return a structured object. Before building out your function, set up a way to see its output as you work. + +Call `parseCard` with `rawCatalogCards[2]` as the argument, assign the result to a variable named `cardResult`, and log `cardResult` to the console. + +Right now `parseCard` has no body, so it returns `undefined`. As you add code to the function over the next steps, the logged output will show you exactly what the function is returning at each stage. + +# --hints-- + +You should call `parseCard` with `rawCatalogCards[2]` as the argument. + +```js +assert.match(__helpers.removeJSComments(code), /parseCard\s*\(\s*rawCatalogCards\s*\[\s*2\s*\]\s*\)/); +``` + +You should assign the result of calling `parseCard` with `rawCatalogCards[2]` to a variable named `cardResult`. + +```js +assert.match(__helpers.removeJSComments(code), /(const|let|var)\s+cardResult\s*=\s*parseCard\s*\(\s*rawCatalogCards\s*\[\s*2\s*\]\s*\)\s*;?/); +``` + +You should log `cardResult` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*cardResult\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) {} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885454.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885454.md new file mode 100644 index 00000000000..8ea565ad1f8 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885454.md @@ -0,0 +1,79 @@ +--- +id: 699d20623e57e8ffee885454 +title: Step 4 +challengeType: 1 +dashedName: step-4 +--- + +# --description-- + +The first step in parsing a catalog card is separating the four fields. The `.split()` method breaks a string into an array of substrings at every occurrence of the separator you provide. + +For example: + +```js +"The Shining | King, Stephen | 1977 | Shelf K1".split("|") +// ["The Shining ", " King, Stephen ", " 1977 ", " Shelf K1"] +``` + +Inside `parseCard`, call `.split("|")` on `rawString` and return the result. After this step, the console should show an array of four strings. + +**Note:** You may notice extra whitespace around the values. You will clean that up in the next step. + +# --hints-- + +You should call `.split("|")` on `rawString` within your `parseCard` function. + +```js +assert.match(parseCard.toString(), /rawString\s*\.\s*split\s*\(\s*('|"|`)\|\1\s*\)\s*;?/); +``` + +`parseCard` should return an array. + +```js +assert.isArray(parseCard("A | B | C | D")); +``` + +When called with `"A | B | C | D"`, `parseCard` should return an array with four elements. + +```js +assert.strictEqual(parseCard("A | B | C | D").length, 4); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { +--fcc-editable-region-- + +--fcc-editable-region-- +} + +const cardResult = parseCard(rawCatalogCards[2]); +console.log(cardResult); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885455.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885455.md new file mode 100644 index 00000000000..1ab8c25c37e --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885455.md @@ -0,0 +1,76 @@ +--- +id: 699d20623e57e8ffee885455 +title: Step 5 +challengeType: 1 +dashedName: step-5 +--- + +# --description-- + +As you can see from the output in the console, after splitting, each part still has extra leading and/or trailing whitespace. To clean every element, you need to process each one individually. + +This is a perfect use case for a `for` loop, which repeats a block of code once for each element in an iterable, such as an array: + +```js +for (let i = 0; i < array.length; i++) { + // code to run on each iteration +} +``` + +`i` starts at `0` (the first index), the loop continues while `i` is less than the array's length, and `i++` increases `i` by one after each iteration. + +Inside `parseCard`, declare an empty array named `trimmedParts`. Then add a `for` loop that iterates over `parts`. Leave the loop body empty for now, as you'll fill it in the next step. + +# --hints-- + +You should declare a variable named `trimmedParts` set to an empty array. + +```js +assert.match(parseCard.toString(), /(const|let|var)\s+trimmedParts\s*=\s*\[\s*\]/); +``` + +You should use a `for` loop to iterate over `parts`. + +```js +assert.match(parseCard.toString(), /for\s*\(\s*(var|let)\s+(\w+)\s*=\s*0\s*;\s*\2\s*<\s*parts\s*\.\s*length\s*;\s*\2\+\+\s*\)\s*\{/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); +--fcc-editable-region-- + +--fcc-editable-region-- + return parts; +} + +const cardResult = parseCard(rawCatalogCards[2]); +console.log(cardResult); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885456.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885456.md new file mode 100644 index 00000000000..c3856659013 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885456.md @@ -0,0 +1,81 @@ +--- +id: 699d20623e57e8ffee885456 +title: Step 7 +challengeType: 1 +dashedName: step-7 +--- + +# --description-- + +Now that the parts are clean, you can transform them into a structured object. In JavaScript, objects use curly braces with "key: value" pairs. For example, `{ title: "Dune", year: 1965 }`. + +Declare four variables: `title`, `author`, `year`, and `location`. Assign to each of them the corresponding element from `trimmedParts`. Then update the `return` statement to return an object with those four properties. + +# --hints-- + +`parseCard` should return an object with `title`, `author`, `year`, and `location` properties. + +```js +const result = parseCard("The Stand | King, Stephen | 1978 | Shelf K2"); +assert.isObject(result); +assert.property(result, "title"); +assert.property(result, "author"); +assert.property(result, "year"); +assert.property(result, "location"); +``` + +The `title` property of the object returned by `parseCard("The Stand | King, Stephen | 1978 | Shelf K2")` should have the value `"The Stand"`. + +```js +assert.strictEqual(parseCard("The Stand | King, Stephen | 1978 | Shelf K2").title, "The Stand"); +``` + +The `author` property of the object returned by `parseCard("The Stand | King, Stephen | 1978 | Shelf K2")` should have the value `"King, Stephen"`. + +```js +assert.strictEqual(parseCard("The Stand | King, Stephen | 1978 | Shelf K2").author, "King, Stephen"); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } +--fcc-editable-region-- + + return trimmedParts; +--fcc-editable-region-- +} + +const cardResult = parseCard(rawCatalogCards[2]); +console.log(cardResult); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885457.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885457.md new file mode 100644 index 00000000000..2a6665fe3c0 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885457.md @@ -0,0 +1,98 @@ +--- +id: 699d20623e57e8ffee885457 +title: Step 8 +challengeType: 1 +dashedName: step-8 +--- + +# --description-- + +Some catalog cards have missing fields. For example, entry 16 has a blank author, so `parseCard` currently returns an empty string for that field. + +The `||` operator provides a fallback: `a || b` evaluates to `a` when `a` is truthy, and `b` otherwise. Since an empty string is falsy, `"" || "Unknown"` evaluates to `"Unknown"`. + +Update `title`, `author`, and `location` in the return statement to use `|| "Unknown"` as a fallback. Leave `year` for the next step. + +# --hints-- + +When called with a string that has a blank author field, the `author` property of the returned object should be `"Unknown"`. + +```js +assert.strictEqual(parseCard("X | | 2000 | Y").author, "Unknown"); +``` + +When called with a string that has a blank title field, the `title` property of the returned object should be `"Unknown"`. + +```js +assert.strictEqual(parseCard(" | A | 2000 | Y").title, "Unknown"); +``` + +When called with a string that has a blank location field, the `location` property of the returned object should be `"Unknown"`. + +```js +assert.strictEqual(parseCard("X | A | 2000 | ").location, "Unknown"); +``` + +You should not add a fallback for `year` yet. + +```js +assert.strictEqual(parseCard("X | A | | Y").year, ""); +``` + +When all fields are present, `parseCard` should return their actual values and not `"Unknown"`. + +```js +assert.strictEqual(parseCard("The Stand | King, Stephen | 1978 | Shelf K2").title, "The Stand"); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; +--fcc-editable-region-- + return { + title: title, + author: author, + year: year, + location: location + }; +--fcc-editable-region-- +} + +const cardResult = parseCard(rawCatalogCards[2]); +console.log(cardResult); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885458.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885458.md new file mode 100644 index 00000000000..525c943ce56 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885458.md @@ -0,0 +1,90 @@ +--- +id: 699d20623e57e8ffee885458 +title: Step 9 +challengeType: 1 +dashedName: step-9 +--- + +# --description-- + +The year field arrives as a string, but you need it as a number for a later calculation. Use `parseInt()` to convert it. + +The year can also be blank, so you need a fallback. The ternary operator `condition ? valueIfTrue : valueIfFalse` returns one of two values based on a condition. + +Update the `year` property in the return statement to: + +```js +year: year ? parseInt(year) : "Unknown" +``` + +# --hints-- + +When called with a card that has a valid year, the `year` property of the returned object should be a number, not a string. + +```js +assert.isNumber(parseCard("The Shining | King, Stephen | 1977 | Shelf K1").year); +``` + +When called with `"The Shining | King, Stephen | 1977 | Shelf K1"`, the `year` property should be `1977`. + +```js +assert.strictEqual(parseCard("The Shining | King, Stephen | 1977 | Shelf K1").year, 1977); +``` + +When called with a string that has a blank year field, the `year` property should be `"Unknown"`. + +```js +assert.strictEqual(parseCard("X | A | | Y").year, "Unknown"); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; +--fcc-editable-region-- + return { + title: title || "Unknown", + author: author || "Unknown", + year: year, + location: location || "Unknown" + }; +--fcc-editable-region-- +} + +const cardResult = parseCard(rawCatalogCards[2]); +console.log(cardResult); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885459.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885459.md new file mode 100644 index 00000000000..88811f04f0e --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885459.md @@ -0,0 +1,92 @@ +--- +id: 699d20623e57e8ffee885459 +title: Step 10 +challengeType: 1 +dashedName: step-10 +--- + +# --description-- + +`parseCard` has been tested and works correctly. The `cardResult` variable is no longer needed. + +Start by removing the `const cardResult` line and `console.log(cardResult)` line. Then create a function named `parseCatalog` that takes `rawCards` as a parameter. For now, declare an empty array named `catalog` inside the function and return it. + +Below the function, call `parseCatalog(rawCatalogCards)`, assign the result to a variable named `catalog`, and log `catalog.length`. + +# --hints-- + +`parseCatalog` should be a function that returns an array. + +```js +assert.isArray(parseCatalog(rawCatalogCards)); +``` + +`catalog` should be defined and set to `parseCatalog(rawCatalogCards)`. + +```js +assert.match(__helpers.removeJSComments(code), /(const|let|var)\s+catalog\s*=\s*parseCatalog\s*\(/); +``` + +You should delete the `cardResult` variable and the related `console.log()` call. + +```js +assert.notMatch(__helpers.removeJSComments(code), /\bcardResult\b/); +``` + +You should log `catalog.length` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*catalog\.length\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +--fcc-editable-region-- +const cardResult = parseCard(rawCatalogCards[2]); +console.log(cardResult); +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545a.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545a.md new file mode 100644 index 00000000000..6bbdc265144 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545a.md @@ -0,0 +1,95 @@ +--- +id: 699d20623e57e8ffee88545a +title: Step 12 +challengeType: 1 +dashedName: step-12 +--- + +# --description-- + +With the catalog parsed, you can search it by field. `findByAuthor` will filter entries whose author contains a search term. + +Create a function named `findByAuthor` that takes `catalog` and `author` as parameters. Inside, declare a variable named `searchTerm` and set it to the lowercase version of `author` to enable case-insensitive matching later. + +Also declare a variable named `results` and assign it an empty array to hold the matches. + +# --hints-- + +You should have a function named `findByAuthor` with `catalog` and `author` as parameters. + +```js +const regex = __helpers.functionRegex('findByAuthor', ['catalog', 'author']); +assert.match(__helpers.removeJSComments(code), regex); +``` + +You should declare a variable named `searchTerm` set to `author.toLowerCase()`. + +```js +assert.match(findByAuthor.toString(), /searchTerm\s*=\s*author\.toLowerCase\s*\(\s*\)/); +``` + +You should declare a variable named `results` set to an empty array. + +```js +assert.match(findByAuthor.toString(), /(const|let|var)\s+results\s*=\s*\[\s*\]/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545b.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545b.md new file mode 100644 index 00000000000..8e1ae94d1a4 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545b.md @@ -0,0 +1,109 @@ +--- +id: 699d20623e57e8ffee88545b +title: Step 15 +challengeType: 1 +dashedName: step-15 +--- + +# --description-- + +Let's verify the search works. Stephen King has six books in the catalog. + +Call `findByAuthor(catalog, "king")` and assign the result to a variable named `kingBooks`. Log `kingBooks.length` to confirm the count. + +Then use a `for` loop to log each result. Use a template literal to include the title and year, like `` `${kingBooks[i].title} (${kingBooks[i].year})` ``. + +# --hints-- + +`kingBooks` should have a length of `6`. + +```js +assert.strictEqual(kingBooks.length, 6); +``` + +You should log `kingBooks.length` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*kingBooks\.length\s*\)/); +``` + +You should use a `for` loop to log each book's title and year. + +```js +assert.match(__helpers.removeJSComments(code), /for\s*\(\s*(var|let)\s+(\w+)\s*=\s*0\s*;\s*\2\s*<\s*kingBooks\s*\.\s*length\s*;\s*\2\+\+\s*\)\s*\{/); +assert.match(__helpers.removeJSComments(code), /kingBooks\[.+\]\.title\b/); +assert.match(__helpers.removeJSComments(code), /kingBooks\[.+\]\.year\b/); +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545c.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545c.md new file mode 100644 index 00000000000..02c8662b5bf --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545c.md @@ -0,0 +1,111 @@ +--- +id: 699d20623e57e8ffee88545c +title: Step 17 +challengeType: 1 +dashedName: step-17 +--- + +# --description-- + +Now you'll build `groupByDecade`, which organizes the catalog by decade. Each key will be a string like `"1970s"` and each value an array of books from that period. + +Create a function named `groupByDecade` that takes `catalog` as a parameter. Inside, declare `const grouped = {}`. Add a `for` loop to iterate over `catalog`, and inside the loop declare `const book = catalog[i]`. + +# --hints-- + +You should have a function named `groupByDecade` with a `catalog` parameter. + +```js +const regex = __helpers.functionRegex('groupByDecade', ['catalog']); +assert.match(__helpers.removeJSComments(code), regex); +``` + +You should declare a variable named `grouped` set to an empty object. + +```js +assert.match(groupByDecade.toString(), /(const|let|var)\s+grouped\s*=\s*\{\s*\}/); +``` + +You should use a `for` loop to iterate over `catalog`. + +```js +assert.match(groupByDecade.toString(), /for\s*\(\s*(var|let)\s+(\w+)\s*=\s*0\s*;\s*\2\s*<\s*catalog\s*\.\s*length\s*;\s*\2\+\+\s*\)\s*\{/); +``` + +You should declare a variable named `book` set to the current catalog entry. + +```js +assert.match(groupByDecade.toString(), /(const|let|var)\s+book\s*=\s*catalog\[/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545d.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545d.md new file mode 100644 index 00000000000..d43e565d95e --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545d.md @@ -0,0 +1,127 @@ +--- +id: 699d20623e57e8ffee88545d +title: Step 19 +challengeType: 1 +dashedName: step-19 +--- + +# --description-- + +After the `"Unknown"` check, add the decade-grouping logic for books with numeric years. Declare `const decade = Math.floor(book.year / 10) * 10`. For example, `Math.floor(1977 / 10)` gives `197`, times `10` gives `1970`. + +Then declare `const decadeKey =` `` `${decade}s` `` to produce a string like `"1970s"`. + +Use bracket notation to group the book: check if `grouped[decadeKey]` doesn't exist yet and initialize it as an empty array, then push `book` into it. Bracket notation is required here because the key is stored in a variable. + +# --hints-- + +You should declare a variable named `decade` set to `Math.floor(book.year / 10) * 10`. + +```js +assert.match(groupByDecade.toString(), /(const|let|var)\s+decade\s*=\s*Math\.floor\s*\(\s*book\.year\s*\/\s*10\s*\)\s*\*\s*10/); +``` + +You should declare a variable named `decadeKey` that includes `decade` and the letter `"s"`. + +```js +assert.match(groupByDecade.toString(), /(const|let|var)\s+decadeKey\s*=.*decade/s); +assert.match(groupByDecade.toString(), /decadeKey.*s/s); +``` + +You should check if `grouped[decadeKey]` doesn't exist yet and initialize it as an empty array. + +```js +assert.match(groupByDecade.toString(), /!\s*grouped\s*\[\s*decadeKey\s*\]/); +assert.match(groupByDecade.toString(), /grouped\s*\[\s*decadeKey\s*\]\s*=\s*\[\s*\]/); +``` + +You should push `book` into `grouped[decadeKey]`. + +```js +assert.match(groupByDecade.toString(), /grouped\s*\[\s*decadeKey\s*\]\s*\.push\s*\(\s*book\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } +--fcc-editable-region-- + +--fcc-editable-region-- + } +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545e.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545e.md new file mode 100644 index 00000000000..b8b41fc49c1 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545e.md @@ -0,0 +1,135 @@ +--- +id: 699d20623e57e8ffee88545e +title: Step 20 +challengeType: 1 +dashedName: step-20 +--- + +# --description-- + +The loop is complete. Return `grouped` from the function. + +Then, below the function, call `groupByDecade(catalog)` and assign the result to a variable named `byDecade`. Log `byDecade` to inspect the grouped result, and you should see an object with keys like `"1950s"`, `"1960s"`, `"1970s"`, and so on, each pointing to an array of books from that decade. + +# --hints-- + +`groupByDecade` should return an object. + +```js +assert.isObject(groupByDecade(catalog)); +``` + +`byDecade["1980s"].length` should be `5`. + +```js +assert.strictEqual(groupByDecade(catalog)["1980s"].length, 5); +``` + +`byDecade["Unknown"].length` should be `1`. + +```js +assert.strictEqual(groupByDecade(catalog)["Unknown"].length, 1); +``` + +You should assign the result of `groupByDecade(catalog)` to a variable named `byDecade`. + +```js +assert.match(__helpers.removeJSComments(code), /(const|let|var)\s+byDecade\s*=\s*groupByDecade\s*\(\s*catalog\s*\)/); +``` + +You should log `byDecade` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*byDecade\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } +--fcc-editable-region-- + +} +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545f.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545f.md new file mode 100644 index 00000000000..f6d41528919 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee88545f.md @@ -0,0 +1,133 @@ +--- +id: 699d20623e57e8ffee88545f +title: Step 21 +challengeType: 1 +dashedName: step-21 +--- + +# --description-- + +The `byDecade` log has served its purpose. Remove `console.log(byDecade)` from your code. + +Then, to make catalog entries easier to scan, create a function named `renderEntry` that takes `entry` as a parameter. Leave the function body empty for now. Below the function, log `renderEntry(catalog[0])`. + +# --hints-- + +`renderEntry` should be a function. + +```js +assert.isFunction(renderEntry); +``` + +`renderEntry` should be a function with an `entry` parameter. + +```js +const regex = __helpers.functionRegex('renderEntry', ['entry']); +assert.match(__helpers.removeJSComments(code), regex); +``` + +The `console.log(byDecade)` call should be removed. + +```js +assert.notMatch(__helpers.removeJSComments(code), /console\.log\s*\(\s*byDecade\s*\)/); +``` + +You should log `renderEntry(catalog[0])`. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*renderEntry\s*\(\s*catalog\s*\[\s*0\s*\]\s*\)\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); +--fcc-editable-region-- +console.log(byDecade); +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885460.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885460.md new file mode 100644 index 00000000000..c24135fd615 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885460.md @@ -0,0 +1,158 @@ +--- +id: 699d20623e57e8ffee885460 +title: Step 23 +challengeType: 1 +dashedName: step-23 +--- + +# --description-- + +Before trusting parsed data, start by validating required fields. + +Create a function named `validateEntry` that takes `entry` as a parameter. Declare `let isValid = true`. + +Add a validation check for `title`: set `isValid = false` if `title` is missing, falsy, or equals `"Unknown"`. + +Return `isValid`. You will add checks for the other fields in the next steps. + +# --hints-- + +`validateEntry` should return a boolean. + +```js +assert.isBoolean(validateEntry({ title: "T", author: "A", year: 2000, location: "L" })); +``` + +`validateEntry({ title: "T", author: "A", year: 2000, location: "L" })` should return `true`. + +```js +assert.isTrue(validateEntry({ title: "T", author: "A", year: 2000, location: "L" })); +``` + +`validateEntry({})` should return `false`. + +```js +assert.isFalse(validateEntry({})); +``` + +`validateEntry({ title: "", author: "A", year: 2000, location: "L" })` should return `false`. + +```js +assert.isFalse(validateEntry({ title: "", author: "A", year: 2000, location: "L" })); +``` + +`validateEntry({ title: "Unknown", author: "A", year: 2000, location: "L" })` should return `false`. + +```js +assert.isFalse(validateEntry({ title: "Unknown", author: "A", year: 2000, location: "L" })); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885461.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885461.md new file mode 100644 index 00000000000..305897b004c --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885461.md @@ -0,0 +1,155 @@ +--- +id: 699d20623e57e8ffee885461 +title: Step 24 +challengeType: 1 +dashedName: step-24 +--- + +# --description-- + +The same pattern applies to the other three fields. Add equivalent checks for `author`, `year`, and `location`. + +Note that `isValid` accumulates across all four checks: if any field fails, it becomes `false` and stays `false` even if the remaining checks pass. This means the function always inspects every field rather than stopping at the first problem. + +# --hints-- + +`validateEntry(catalog[0])` should return `true`. + +```js +assert.isTrue(validateEntry(catalog[0])); +``` + +`validateEntry(catalog[16])` should return `false`. + +```js +assert.isFalse(validateEntry(catalog[16])); +``` + +`validateEntry(catalog[17])` should return `false`. + +```js +assert.isFalse(validateEntry(catalog[17])); +``` + +`validateEntry(catalog[18])` should return `false` because the location is `"Unknown"`. + +```js +assert.isFalse(validateEntry(catalog[18])); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } +--fcc-editable-region-- + +--fcc-editable-region-- + return isValid; +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885462.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885462.md new file mode 100644 index 00000000000..8ff1b5c90cb --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/699d20623e57e8ffee885462.md @@ -0,0 +1,153 @@ +--- +id: 699d20623e57e8ffee885462 +title: Step 25 +challengeType: 1 +dashedName: step-25 +--- + +# --description-- + +Let's see the validator catch a real problem. `catalog[0]` is "From a Buick 8" by Stephen King, which has all four fields populated. `catalog[16]` is "Unknown Title", which has a blank author field. + +Log `validateEntry(catalog[0])` and `validateEntry(catalog[16])`. The first should return `true`; the second should return `false`. + +# --hints-- + +You should log `validateEntry(catalog[0])` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*validateEntry\s*\(\s*catalog\s*\[\s*0\s*\]\s*\)\s*\)/); +``` + +You should log `validateEntry(catalog[16])` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*validateEntry\s*\(\s*catalog\s*\[\s*16\s*\]\s*\)\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } + if (!("author" in entry) || !entry.author || entry.author === "Unknown") { + isValid = false; + } + if (!("year" in entry) || !entry.year || entry.year === "Unknown") { + isValid = false; + } + if (!("location" in entry) || !entry.location || entry.location === "Unknown") { + isValid = false; + } + return isValid; +} + +--fcc-editable-region-- + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9952.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9952.md new file mode 100644 index 00000000000..01fcc48c062 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9952.md @@ -0,0 +1,175 @@ +--- +id: 69a65722a69bc6f8d87a9952 +title: Step 26 +challengeType: 1 +dashedName: step-26 +--- + +# --description-- + +JSON (JavaScript Object Notation) is a standard format for sharing data between systems. `JSON.stringify` converts a JavaScript value into a JSON-formatted string. + +Start by removing the two `validateEntry` console.log calls, as they were only needed for testing. Then create a function named `exportToJSON` that takes `catalog` as a parameter. Return `JSON.stringify(catalog, null, 2)`. The first argument is the data to convert. The second argument (`null`) means no custom filtering. The third (`2`) adds two-space indentation, making the output readable. + +Below the function, log `exportToJSON(catalog.slice(0, 2))` to preview the JSON output for the first two entries. + +# --hints-- + +You should remove the `validateEntry` console.log calls. + +```js +assert.notMatch(__helpers.removeJSComments(code), /console\.log\s*\(\s*validateEntry/); +``` + +`exportToJSON` should return a string. + +```js +assert.isString(exportToJSON(catalog)); +``` + +`JSON.parse(exportToJSON(catalog))` should produce a valid array. + +```js +assert.isArray(JSON.parse(exportToJSON(catalog))); +``` + +You should use `JSON.stringify` with `null` and `2` as the second and third arguments. + +```js +assert.match(exportToJSON.toString(), /JSON\.stringify\s*\(\s*catalog\s*,\s*null\s*,\s*2\s*\)/); +``` + +You should log `exportToJSON(catalog.slice(0, 2))` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*exportToJSON\s*\(/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } + if (!("author" in entry) || !entry.author || entry.author === "Unknown") { + isValid = false; + } + if (!("year" in entry) || !entry.year || entry.year === "Unknown") { + isValid = false; + } + if (!("location" in entry) || !entry.location || entry.location === "Unknown") { + isValid = false; + } + return isValid; +} + +--fcc-editable-region-- +console.log(validateEntry(catalog[0])); +console.log(validateEntry(catalog[16])); + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9953.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9953.md new file mode 100644 index 00000000000..cb87c73e39b --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9953.md @@ -0,0 +1,172 @@ +--- +id: 69a65722a69bc6f8d87a9953 +title: Step 27 +challengeType: 1 +dashedName: step-27 +--- + +# --description-- + +CSV (Comma-Separated Values) is a format used for spreadsheets and data import. Each line represents one entry with fields separated by commas. + +Start by removing the `console.log(exportToJSON(...))` call, as it was only needed for testing. Then create a function named `exportToCSV` that takes `catalog` as a parameter. Inside, declare `const header = "Title,Author,Year,Location"` and `const rows = []`. + +# --hints-- + +You should remove the `exportToJSON` console.log call. + +```js +assert.notMatch(__helpers.removeJSComments(code), /console\.log\s*\(\s*exportToJSON/); +``` + +You should have a function named `exportToCSV` with a `catalog` parameter. + +```js +const regex = __helpers.functionRegex('exportToCSV', ['catalog']); +assert.match(__helpers.removeJSComments(code), regex); +``` + +You should declare a variable named `header` with value `"Title,Author,Year,Location"`. + +```js +assert.match(exportToCSV.toString(), /Title,Author,Year,Location/); +``` + +You should declare a variable named `rows` set to an empty array. + +```js +assert.match(exportToCSV.toString(), /(const|let|var)\s+rows\s*=\s*\[\s*\]/); +``` + + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } + if (!("author" in entry) || !entry.author || entry.author === "Unknown") { + isValid = false; + } + if (!("year" in entry) || !entry.year || entry.year === "Unknown") { + isValid = false; + } + if (!("location" in entry) || !entry.location || entry.location === "Unknown") { + isValid = false; + } + return isValid; +} + +function exportToJSON(catalog) { + return JSON.stringify(catalog, null, 2); +} + +--fcc-editable-region-- +console.log(exportToJSON(catalog.slice(0, 2))); + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9954.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9954.md new file mode 100644 index 00000000000..41c78238472 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a65722a69bc6f8d87a9954.md @@ -0,0 +1,364 @@ +--- +id: 69a65722a69bc6f8d87a9954 +title: Step 30 +challengeType: 1 +dashedName: step-30 +--- + +# --description-- + +Let's finish with a summary. Log `catalog.length` and `Object.keys(byDecade).length` to see total books and decade groups. + +Declare `let oldestYear = Infinity` and `let newestYear = 0`. Use a `for` loop over `catalog`: for each entry where the year is not `"Unknown"`, update `oldestYear` if the year is smaller and `newestYear` if it is larger. Log both values. + +You have completed the Heritage Library Catalog workshop! + +# --hints-- + +You should log `catalog.length` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*catalog\.length\s*\)/); +``` + +You should log `Object.keys(byDecade).length` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*Object\.keys\s*\(\s*byDecade\s*\)\.length\s*\)/); +``` + +You should use a `for` loop over `catalog` to find the oldest and newest years. + +```js +assert.match(__helpers.removeJSComments(code), /for\s*\(\s*(var|let)\s+(\w+)\s*=\s*0\s*;\s*\2\s*<\s*catalog\s*\.\s*length\s*;\s*\2\+\+\s*\)\s*\{/); +``` + +The oldest year in the catalog should be `850`. + +```js +assert.strictEqual(oldestYear, 850); +``` + +The newest year in the catalog should be `2011`. + +```js +assert.strictEqual(newestYear, 2011); +``` + +You should log `oldestYear` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*oldestYear\s*\)/); +``` + +You should log `newestYear` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*newestYear\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } + if (!("author" in entry) || !entry.author || entry.author === "Unknown") { + isValid = false; + } + if (!("year" in entry) || !entry.year || entry.year === "Unknown") { + isValid = false; + } + if (!("location" in entry) || !entry.location || entry.location === "Unknown") { + isValid = false; + } + return isValid; +} + +function exportToJSON(catalog) { + return JSON.stringify(catalog, null, 2); +} + +function exportToCSV(catalog) { + const header = "Title,Author,Year,Location"; + const rows = []; + for (let i = 0; i < catalog.length; i++) { + const entry = catalog[i]; + rows.push(`"${entry.title}","${entry.author}",${entry.year},"${entry.location}"`); + } + let csv = header; + for (let i = 0; i < rows.length; i++) { + csv = csv + "\n" + rows[i]; + } + return csv; +} + +console.log(exportToCSV(catalog)); + +--fcc-editable-region-- + +--fcc-editable-region-- +``` + +# --solutions-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } + if (!("author" in entry) || !entry.author || entry.author === "Unknown") { + isValid = false; + } + if (!("year" in entry) || !entry.year || entry.year === "Unknown") { + isValid = false; + } + if (!("location" in entry) || !entry.location || entry.location === "Unknown") { + isValid = false; + } + return isValid; +} + +function exportToJSON(catalog) { + return JSON.stringify(catalog, null, 2); +} + +function exportToCSV(catalog) { + const header = "Title,Author,Year,Location"; + const rows = []; + for (let i = 0; i < catalog.length; i++) { + const entry = catalog[i]; + rows.push(`"${entry.title}","${entry.author}",${entry.year},"${entry.location}"`); + } + let csv = header; + for (let i = 0; i < rows.length; i++) { + csv = csv + "\n" + rows[i]; + } + return csv; +} + +console.log(exportToCSV(catalog)); + +console.log(catalog.length); +console.log(Object.keys(byDecade).length); +let oldestYear = Infinity; +let newestYear = 0; +for (let i = 0; i < catalog.length; i++) { + const year = catalog[i].year; + if (year !== "Unknown") { + if (year < oldestYear) { + oldestYear = year; + } + if (year > newestYear) { + newestYear = year; + } + } +} +console.log(oldestYear); +console.log(newestYear); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de737382f5b679d7e169.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de737382f5b679d7e169.md new file mode 100644 index 00000000000..3b37b89e0bf --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de737382f5b679d7e169.md @@ -0,0 +1,85 @@ +--- +id: 69a9de737382f5b679d7e169 +title: Step 13 +challengeType: 1 +dashedName: step-13 +--- + +# --description-- + +To find matching authors, check each catalog entry one by one. + +Add an empty `for` loop inside `findByAuthor` to iterate over `catalog`. You will add the loop body in the next step. + +# --hints-- + +You should use a `for` loop to iterate over `catalog`. + +```js +assert.match(findByAuthor.toString(), /for\s*\(\s*(var|let)\s+(\w+)\s*=\s*0\s*;\s*\2\s*<\s*catalog\s*\.\s*length\s*;\s*\2\+\+\s*\)\s*\{/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; +--fcc-editable-region-- + +--fcc-editable-region-- +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de7ceb9b18f4fc988306.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de7ceb9b18f4fc988306.md new file mode 100644 index 00000000000..99360c5fe10 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de7ceb9b18f4fc988306.md @@ -0,0 +1,97 @@ +--- +id: 69a9de7ceb9b18f4fc988306 +title: Step 14 +challengeType: 1 +dashedName: step-14 +--- + +# --description-- + +Inside the loop, check whether the current entry's author (converted to lowercase) includes `searchTerm`. The `.includes()` method returns `true` if a string contains another string as a substring. If the condition is true, push the entry into `results`. After the loop, return `results`. + +# --hints-- + +`findByAuthor(catalog, "king").length` should be `6`. + +```js +assert.strictEqual(findByAuthor(catalog, "king").length, 6); +``` + +`findByAuthor(catalog, "asimov").length` should be `2`. + +```js +assert.strictEqual(findByAuthor(catalog, "asimov").length, 2); +``` + +`findByAuthor(catalog, "xyz").length` should be `0`. + +```js +assert.strictEqual(findByAuthor(catalog, "xyz").length, 0); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + --fcc-editable-region-- + + } +--fcc-editable-region-- +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de82b435a1310aff57c2.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de82b435a1310aff57c2.md new file mode 100644 index 00000000000..4e19130c97d --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de82b435a1310aff57c2.md @@ -0,0 +1,119 @@ +--- +id: 69a9de82b435a1310aff57c2 +title: Step 18 +challengeType: 1 +dashedName: step-18 +--- + +# --description-- + +Some books have `"Unknown"` as their year. We will handle that edge case first. + +Inside the loop, check if `book.year` is equal to the string `"Unknown"`. Then, check if `grouped["Unknown"]` doesn't exist yet and initialize it as an empty array. Push `book` into `grouped["Unknown"]`. + +Finally, use `continue` to skip the rest of the loop body. The `continue` statement jumps directly to the next iteration. + +# --hints-- + +You should check if `book.year` is equal to `"Unknown"`. + +```js +assert.match(groupByDecade.toString(), /book\.year\s*===?\s*"Unknown"/); +``` + +You should check if `grouped["Unknown"]` doesn't exist yet and initialize it as an empty array. + +```js +assert.match(groupByDecade.toString(), /!\s*grouped\s*\[\s*"Unknown"\s*\]/); +assert.match(groupByDecade.toString(), /grouped\s*\[\s*"Unknown"\s*\]\s*=\s*\[\s*\]/); +``` + +You should push `book` into `grouped["Unknown"]`. + +```js +assert.match(groupByDecade.toString(), /grouped\s*\[\s*"Unknown"\s*\]\s*\.push\s*\(\s*book\s*\)/); +``` + +You should use `continue` to skip to the next iteration after handling an unknown year. + +```js +assert.match(groupByDecade.toString(), /\bcontinue\b/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; +--fcc-editable-region-- + +--fcc-editable-region-- + } +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de8abda299f753b1afe4.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de8abda299f753b1afe4.md new file mode 100644 index 00000000000..7f0852e5f58 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de8abda299f753b1afe4.md @@ -0,0 +1,168 @@ +--- +id: 69a9de8abda299f753b1afe4 +title: Step 28 +challengeType: 1 +dashedName: step-28 +--- + +# --description-- + +Add a `for` loop over `catalog`. Inside the loop, declare `const entry = catalog[i]`. Build a row string with the four fields separated by commas and push it into `rows`. + +String fields need literal double-quote characters in the output (e.g. `"King, Stephen"`), so that commas inside values don't break the CSV format. Since `year` is a number, it doesn't need them. + +# --hints-- + +You should use a `for` loop to iterate over `catalog`. + +```js +assert.match(exportToCSV.toString(), /for\s*\(\s*(var|let)\s+(\w+)\s*=\s*0\s*;\s*\2\s*<\s*catalog\s*\.\s*length\s*;\s*\2\+\+\s*\)\s*\{/); +``` + +Each row should have the entry's fields separated by commas. + +```js +assert.match(exportToCSV.toString(), /rows\.push/); +``` + +String fields should be wrapped in double quotes. + +```js +assert.match(__helpers.removeJSComments(code), /"\$\{entry\./); + +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } + if (!("author" in entry) || !entry.author || entry.author === "Unknown") { + isValid = false; + } + if (!("year" in entry) || !entry.year || entry.year === "Unknown") { + isValid = false; + } + if (!("location" in entry) || !entry.location || entry.location === "Unknown") { + isValid = false; + } + return isValid; +} + +function exportToJSON(catalog) { + return JSON.stringify(catalog, null, 2); +} + +function exportToCSV(catalog) { + const header = "Title,Author,Year,Location"; + const rows = []; +--fcc-editable-region-- + +--fcc-editable-region-- +} +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de93aa714a3b0105b051.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de93aa714a3b0105b051.md new file mode 100644 index 00000000000..8b81d950526 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de93aa714a3b0105b051.md @@ -0,0 +1,178 @@ +--- +id: 69a9de93aa714a3b0105b051 +title: Step 29 +challengeType: 1 +dashedName: step-29 +--- + +# --description-- + +Now combine all rows into one CSV string you can export or share. + +After the first loop, declare `let csv = header`. Add another `for` loop to append each row to `csv` with a newline: `csv = csv + "\n" + rows[i]`. Return `csv`. Then, below the function, log `exportToCSV(catalog)` to preview the formatted output. + +# --hints-- + +`exportToCSV` should return a string. + +```js +assert.isString(exportToCSV(catalog)); +``` + +The first line of the returned string should be `"Title,Author,Year,Location"`. + +```js +assert.strictEqual(exportToCSV(catalog).split("\n")[0], "Title,Author,Year,Location"); +``` + +The output should contain `"King, Stephen"` (quoted to handle the comma). + +```js +assert.include(exportToCSV(catalog), '"King, Stephen"'); +``` + +You should log `exportToCSV(catalog)` to the console. + +```js +assert.match(__helpers.removeJSComments(code), /console\.log\s*\(\s*exportToCSV\s*\(\s*catalog\s*\)\s*\)/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { + const title = entry.title || "Unknown"; + const author = entry.author || "Unknown"; + const year = entry.year || "Unknown"; + const location = entry.location || "Unknown"; + return `${"-".repeat(25)} +Title: ${title} +Author: ${author} +Year: ${year} +Location: ${location} +${"-".repeat(25)}`; +} + +console.log(renderEntry(catalog[0])); + +function validateEntry(entry) { + let isValid = true; + if (!("title" in entry) || !entry.title || entry.title === "Unknown") { + isValid = false; + } + if (!("author" in entry) || !entry.author || entry.author === "Unknown") { + isValid = false; + } + if (!("year" in entry) || !entry.year || entry.year === "Unknown") { + isValid = false; + } + if (!("location" in entry) || !entry.location || entry.location === "Unknown") { + isValid = false; + } + return isValid; +} + +function exportToJSON(catalog) { + return JSON.stringify(catalog, null, 2); +} + +function exportToCSV(catalog) { + const header = "Title,Author,Year,Location"; + const rows = []; + for (let i = 0; i < catalog.length; i++) { + const entry = catalog[i]; + rows.push(`"${entry.title}","${entry.author}",${entry.year},"${entry.location}"`); + } +--fcc-editable-region-- + +} + +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69aa11c2584523e6e991330c.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69aa11c2584523e6e991330c.md new file mode 100644 index 00000000000..f133092b20e --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69aa11c2584523e6e991330c.md @@ -0,0 +1,85 @@ +--- +id: 69aa11c2584523e6e991330c +title: Step 6 +challengeType: 1 +dashedName: step-6 +--- + +# --description-- + +The `.trim()` method removes whitespace from both ends of a string. For example, `" hello ".trim()` returns `"hello"`. + +Inside the loop body, push `parts[i].trim()` into `trimmedParts`. Then update the existing `return` statement to return `trimmedParts` instead of `parts`. + +After this step, the logged output should show clean values without extra spaces. + +# --hints-- + +You should push `parts[i].trim()` into `trimmedParts` inside the loop. + +```js +assert.match(parseCard.toString(), /trimmedParts\s*\.\s*push\s*\(\s*parts\s*\[\s*\w+\s*\]\s*\.\s*trim\s*\(\s*\)\s*\)/); +``` + +`parseCard` should return `trimmedParts`. + +```js +const explorer = await __helpers.Explorer(code); +const { parseCard } = explorer.allFunctions; +assert.isTrue(parseCard.hasReturn("trimmedParts")); +``` + +`parseCard` should return an array where each element has no leading or trailing whitespace. + +```js +assert.isArray(parseCard(" A | B | C | D ")); +``` + +When called with `" A | B | C | D "`, the first element of the returned array should be `"A"`. + +```js +assert.strictEqual(parseCard(" A | B | C | D ")[0], "A"); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { +--fcc-editable-region-- + + } + return parts; +--fcc-editable-region-- +} + +const cardResult = parseCard(rawCatalogCards[2]); +console.log(cardResult); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69b8f3a2c1d4e5f6a7b8c9d0.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69b8f3a2c1d4e5f6a7b8c9d0.md new file mode 100644 index 00000000000..38f4b0c22bd --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69b8f3a2c1d4e5f6a7b8c9d0.md @@ -0,0 +1,88 @@ +--- +id: 69b8f3a2c1d4e5f6a7b8c9d0 +title: Step 11 +challengeType: 1 +dashedName: step-11 +--- + +# --description-- + +`parseCatalog` currently returns an empty array. To fill it, add a `for` loop inside the function that iterates over `rawCards`. Inside the loop, call `parseCard` on each element and push the result into `catalog`. + +# --hints-- + +`parseCatalog(rawCatalogCards).length` should be `19`. + +```js +assert.strictEqual(parseCatalog(rawCatalogCards).length, 19); +``` + +`parseCatalog` should use a `for` loop to iterate over `rawCards`. + +```js +assert.match(parseCatalog.toString(), /for\s*\(\s*(var|let)\s+(\w+)\s*=\s*0\s*;\s*\2\s*<\s*rawCards\s*\.\s*length\s*;\s*\2\+\+\s*\)\s*\{/); +``` + +You should call `parseCard` inside the loop. + +```js +assert.match(parseCatalog.toString(), /parseCard\s*\(/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; +--fcc-editable-region-- + +--fcc-editable-region-- + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); +console.log(catalog.length); +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69c2a3b4d5e6f7a8b9c0d1e2.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69c2a3b4d5e6f7a8b9c0d1e2.md new file mode 100644 index 00000000000..8b261a03fd1 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69c2a3b4d5e6f7a8b9c0d1e2.md @@ -0,0 +1,96 @@ +--- +id: 69c2a3b4d5e6f7a8b9c0d1e2 +title: Step 16 +challengeType: 1 +dashedName: step-16 +--- + +# --description-- + +The `kingBooks` test code confirmed that `findByAuthor` works correctly. Before moving on to `groupByDecade`, clean up the console output by removing it. + +Remove the `const kingBooks = ...` line, `console.log(kingBooks.length)`, and the `for` loop that displays the book titles and years. + +# --hints-- + +The `kingBooks` variable, the `console.log(kingBooks.length)` statement, and the `for` loop should all be removed. + +```js +assert.notMatch(__helpers.removeJSComments(code), /\bkingBooks\b/); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +--fcc-editable-region-- +const kingBooks = findByAuthor(catalog, "king"); +console.log(kingBooks.length); +for (let i = 0; i < kingBooks.length; i++) { + console.log(`${kingBooks[i].title} (${kingBooks[i].year})`); +} +--fcc-editable-region-- +``` diff --git a/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69d5e6f7a8b9c0d1e2f3a4b5.md b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69d5e6f7a8b9c0d1e2f3a4b5.md new file mode 100644 index 00000000000..ffd92d7a5be --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69d5e6f7a8b9c0d1e2f3a4b5.md @@ -0,0 +1,149 @@ +--- +id: 69d5e6f7a8b9c0d1e2f3a4b5 +title: Step 22 +challengeType: 1 +dashedName: step-22 +--- + +# --description-- + +Now fill in `renderEntry`. Declare a fallback constant for each field: `const title = entry.title || "Unknown"`, and apply the same pattern for `author`, `year`, and `location`. + +Return a multi-line template literal that formats the entry as a library card. Use `"-".repeat(25)` for the top and bottom border, and include labeled lines for `Title`, `Author`, `Year`, and `Location`. For example, a labeled line for the title would look like `Title: ${title}`. + +# --hints-- + +`renderEntry` should return a string. + +```js +assert.isString(renderEntry(catalog[0])); +``` + +When called with `catalog[0]`, the returned string should contain `"From a Buick 8"`. + +```js +assert.include(renderEntry(catalog[0]), "From a Buick 8"); +``` + +When called with `catalog[0]`, the returned string should contain `"King, Stephen"`. + +```js +assert.include(renderEntry(catalog[0]), "King, Stephen"); +``` + +When called with `catalog[0]`, the returned string should contain the year `2002`. + +```js +assert.include(renderEntry(catalog[0]), "2002"); +``` + +When called with `catalog[0]`, the returned string should contain `"Shelf K7"`. + +```js +assert.include(renderEntry(catalog[0]), "Shelf K7"); +``` + +The returned string should include a top and bottom border using `"-".repeat(25)`. + +```js +assert.isAbove(renderEntry(catalog[0]).split("-".repeat(25)).length, 2); +``` + +# --seed-- + +## --seed-contents-- + +```js +const rawCatalogCards = [ + "From a Buick 8 | King, Stephen | 2002 | Shelf K7", + "The Shining | King, Stephen | 1977 | Shelf K1", + "The Stand | King, Stephen | 1978 | Shelf K2", + "It | King, Stephen | 1986 | Shelf K3", + "Misery | King, Stephen | 1987 | Shelf K4", + "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5", + "I, Robot | Asimov, Isaac | 1950 | Shelf A8", + "Foundation | Asimov, Isaac | 1951 | Shelf A9", + "Dune | Herbert, Frank | 1965 | Shelf H3", + "Neuromancer | Gibson, William | 1984 | Shelf G8", + "Snow Crash | Stephenson, Neal | 1992 | Shelf S6", + "The Martian | Weir, Andy | 2011 | Shelf W5", + "Ender's Game | Card, Orson Scott | 1985 | Shelf C2", + "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1", + "Ready Player One | Cline, Ernest | 2011 | Shelf C7", + "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5", + // edge cases: missing data + "Unknown Title | | 1975 | Shelf X1", + "Mysterious Manuscript | Unknown Author | | Shelf Z9", + "Ancient Scroll | Anonymous | 850 | ", +]; + +function parseCard(rawString) { + const parts = rawString.split("|"); + const trimmedParts = []; + for (let i = 0; i < parts.length; i++) { + trimmedParts.push(parts[i].trim()); + } + const title = trimmedParts[0]; + const author = trimmedParts[1]; + const year = trimmedParts[2]; + const location = trimmedParts[3]; + return { + title: title || "Unknown", + author: author || "Unknown", + year: year ? parseInt(year) : "Unknown", + location: location || "Unknown" + }; +} + +function parseCatalog(rawCards) { + const catalog = []; + for (let i = 0; i < rawCards.length; i++) { + catalog.push(parseCard(rawCards[i])); + } + return catalog; +} + +const catalog = parseCatalog(rawCatalogCards); + +function findByAuthor(catalog, author) { + const searchTerm = author.toLowerCase(); + const results = []; + for (let i = 0; i < catalog.length; i++) { + if (catalog[i].author.toLowerCase().includes(searchTerm)) { + results.push(catalog[i]); + } + } + return results; +} + +function groupByDecade(catalog) { + const grouped = {}; + for (let i = 0; i < catalog.length; i++) { + const book = catalog[i]; + if (book.year === "Unknown") { + if (!grouped["Unknown"]) { + grouped["Unknown"] = []; + } + grouped["Unknown"].push(book); + continue; + } + const decade = Math.floor(book.year / 10) * 10; + const decadeKey = `${decade}s`; + if (!grouped[decadeKey]) { + grouped[decadeKey] = []; + } + grouped[decadeKey].push(book); + } + return grouped; +} + +const byDecade = groupByDecade(catalog); + +function renderEntry(entry) { +--fcc-editable-region-- + +--fcc-editable-region-- +} + +console.log(renderEntry(catalog[0])); +``` diff --git a/curriculum/dictionaries/english/comments.json b/curriculum/dictionaries/english/comments.json index 39cd3ae9fca..d064378d711 100644 --- a/curriculum/dictionaries/english/comments.json +++ b/curriculum/dictionaries/english/comments.json @@ -109,5 +109,6 @@ "fho5t5": "When you open a new tab at the end", "00kcrm": "yields true", "sxpg2a": "Your mailbox, drive, and other work sites", - "n4f332": "TODO: use a different type of loop" + "n4f332": "TODO: use a different type of loop", + "k7m2vx": "edge cases: missing data" } diff --git a/curriculum/structure/blocks/workshop-heritage-library-catalog.json b/curriculum/structure/blocks/workshop-heritage-library-catalog.json new file mode 100644 index 00000000000..70e21aa5e18 --- /dev/null +++ b/curriculum/structure/blocks/workshop-heritage-library-catalog.json @@ -0,0 +1,41 @@ +{ + "isUpcomingChange": false, + "dashedName": "workshop-heritage-library-catalog", + "helpCategory": "JavaScript", + "blockLayout": "challenge-grid", + "challengeOrder": [ + { "id": "699d1f8e51fcdc8230f779fb", "title": "Step 1" }, + { "id": "699d20623e57e8ffee885450", "title": "Step 2" }, + { "id": "699d20623e57e8ffee885451", "title": "Step 3" }, + { "id": "699d20623e57e8ffee885454", "title": "Step 4" }, + { "id": "699d20623e57e8ffee885455", "title": "Step 5" }, + { "id": "69aa11c2584523e6e991330c", "title": "Step 6" }, + { "id": "699d20623e57e8ffee885456", "title": "Step 7" }, + { "id": "699d20623e57e8ffee885457", "title": "Step 8" }, + { "id": "699d20623e57e8ffee885458", "title": "Step 9" }, + { "id": "699d20623e57e8ffee885459", "title": "Step 10" }, + { "id": "69b8f3a2c1d4e5f6a7b8c9d0", "title": "Step 11" }, + { "id": "699d20623e57e8ffee88545a", "title": "Step 12" }, + { "id": "69a9de737382f5b679d7e169", "title": "Step 13" }, + { "id": "69a9de7ceb9b18f4fc988306", "title": "Step 14" }, + { "id": "699d20623e57e8ffee88545b", "title": "Step 15" }, + { "id": "69c2a3b4d5e6f7a8b9c0d1e2", "title": "Step 16" }, + { "id": "699d20623e57e8ffee88545c", "title": "Step 17" }, + { "id": "69a9de82b435a1310aff57c2", "title": "Step 18" }, + { "id": "699d20623e57e8ffee88545d", "title": "Step 19" }, + { "id": "699d20623e57e8ffee88545e", "title": "Step 20" }, + { "id": "699d20623e57e8ffee88545f", "title": "Step 21" }, + { "id": "69d5e6f7a8b9c0d1e2f3a4b5", "title": "Step 22" }, + { "id": "699d20623e57e8ffee885460", "title": "Step 23" }, + { "id": "699d20623e57e8ffee885461", "title": "Step 24" }, + { "id": "699d20623e57e8ffee885462", "title": "Step 25" }, + { "id": "69a65722a69bc6f8d87a9952", "title": "Step 26" }, + { "id": "69a65722a69bc6f8d87a9953", "title": "Step 27" }, + { "id": "69a9de8abda299f753b1afe4", "title": "Step 28" }, + { "id": "69a9de93aa714a3b0105b051", "title": "Step 29" }, + { "id": "69a65722a69bc6f8d87a9954", "title": "Step 30" } + ], + "blockLabel": "workshop", + "usesMultifileEditor": true, + "hasEditableBoundaries": true +} diff --git a/curriculum/structure/superblocks/javascript-v9.json b/curriculum/structure/superblocks/javascript-v9.json index b084ea0d124..9e2f1c4e129 100644 --- a/curriculum/structure/superblocks/javascript-v9.json +++ b/curriculum/structure/superblocks/javascript-v9.json @@ -99,6 +99,7 @@ "lecture-working-with-loops", "workshop-sentence-analyzer", "workshop-space-mission-roster", + "workshop-heritage-library-catalog", "lab-longest-word-in-a-string", "lab-factorial-calculator", "lab-mutations",