diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..dce8a8d8c 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,9 +1,8 @@ // Predict and explain first... // This code should log out the houseNumber from the address object -// but it isn't working... -// Fix anything that isn't working +// Create an object that holds address details const address = { houseNumber: 42, street: "Imaginary Road", @@ -12,4 +11,5 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +// Access the houseNumber property using dot notation +console.log(`My house number is ${address.houseNumber}`); diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..63ad658c6 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,8 +1,8 @@ // Predict and explain first... // This program attempts to log out all the property values in the object. -// But it isn't working. Explain why first and then fix the problem +// Create an object that holds author details const author = { firstName: "Zadie", lastName: "Smith", @@ -11,6 +11,24 @@ const author = { alive: true, }; -for (const value of author) { +// Use Object.values() to get an array of all values, then loop over them +for (const value of Object.values(author)) { console.log(value); } + +/* +## Explanation of the Fix + +- Object.values(author) is a built-in function that takes an object and returns an array containing only the values. In our case it returns: ["Zadie", "Smith", "writer", 40, true] +- Since the result is now an array, for...of works correctly and goes through each value one by one. + +## How to Test in the Terminal +node author.js + +Expected output: +Zadie +Smith +writer +40 +true +*/ diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..f6b6ba1de 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -2,14 +2,36 @@ // This program should log out the title, how many it serves and the ingredients. // Each ingredient should be logged on a new line -// How can you fix it? +// Create an object that holds recipe details const recipe = { title: "bruschetta", serves: 2, ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +// Log the title and how many it serves +console.log(`${recipe.title} serves ${recipe.serves}`); + +// Log each ingredient on a new line using a loop +for (const ingredient of recipe.ingredients) { + console.log(ingredient); +} + +/* +## Brief Explanation + +- recipe.ingredients is an array (a numbered list) containing four items. +- for...of works on arrays, so it goes through each item one by one. +- Each time through the loop, the variable ingredient holds the current item, and console.log prints it on its own line. + +## How to Test +node recipe.js + +Expected output: +bruschetta serves 2 +olive oil +tomatoes +salt +pepper +*/ diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..8604982fb 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,12 @@ -function contains() {} +// Check if an object contains a specific property +function contains(object, propertyName) { + // If the input is not a valid object or is an array, return false + if (typeof object !== "object" || object === null || Array.isArray(object)) { + return false; + } + + // Use hasOwnProperty to check if the key exists in the object + return object.hasOwnProperty(propertyName); +} module.exports = contains; diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..03068e00a 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,16 @@ -function createLookup() { - // implementation here +// Take an array of pairs and return a lookup object +function createLookup(pairs) { + // Create an empty object to store the results + const lookup = {}; + + // Loop through each pair in the array + for (const pair of pairs) { + // pair[0] is the key (country code), pair[1] is the value (currency code) + lookup[pair[0]] = pair[1]; + } + + // Return the completed lookup object + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..708700b5a 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,13 +1,30 @@ +// Parse a query string into an object of key-value pairs function parseQueryString(queryString) { + // Create an empty object to store the results const queryParams = {}; + + // If the input is empty, return the empty object if (queryString.length === 0) { return queryParams; } + + // Split the string by "&" to get each key=value pair const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + // Find the position of the FIRST "=" only + const firstEqualIndex = pair.indexOf("="); + + // If there is no "=", the whole pair is the key with an empty value + if (firstEqualIndex === -1) { + queryParams[pair] = ""; + } else { + // Everything before the first "=" is the key + const key = pair.slice(0, firstEqualIndex); + // Everything after the first "=" is the value (may contain more "=" signs) + const value = pair.slice(firstEqualIndex + 1); + queryParams[key] = value; + } } return queryParams; diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..de1643d2f 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,22 @@ -function tally() {} +// Count the frequency of each item in an array +function tally(items) { + // If the input is not an array, throw an error + if (!Array.isArray(items)) { + throw new Error("Input must be an array"); + } + + // Create an empty object to store the counts + const counts = {}; + + // Loop through each item in the array + for (const item of items) { + // If this item already exists in counts, add 1 to it + // If it does not exist yet, start at 1 + counts[item] = (counts[item] || 0) + 1; + } + + // Return the object with all the counts + return counts; +} module.exports = tally; diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..4060182cd 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -1,29 +1,45 @@ // Let's define how invert should work - // Given an object // When invert is passed this object // Then it should swap the keys and values in the object -// E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} +// E.g. invert({x: 10, y: 20}), target output: {"10": "x", "20": "y"} + +/* +--- Answers to the Interpret Questions --- + +a) What is the return value of invert({a: 1}) in the old broken code? +Answer: It returned { key: 1 } because dot notation (.key) creates a literal string key named "key". + +b) What is the return value of invert({a: 1, b: 2}) in the old broken code? +Answer: It returned { key: 2 } because the literal property "key" is overwritten in the second loop iteration. + +c) What is the target return value of invert({a: 1, b: 2})? +Answer: The target return value is { "1": "a", "2": "b" }. + +c-continued) What does Object.entries do? Why is it needed here? +Answer: Object.entries(obj) converts the object into an array of key-value pairs, like [["a", 1], ["b", 2]]. It is needed because we cannot use a 'for...of' loop directly on a standard object. + +d) Why is the current return value different from the target? +Answer: Because the old code used invertedObj.key (a hardcoded key name) instead of assigning the dynamic value as the new key. +e) How can you fix it? +Answer: By using bracket notation to set the 'value' as the new dynamic key, and assigning the 'key' as its value: invertedObj[value] = key; +*/ + +// Swap keys and values in an object function invert(obj) { + // Create an empty object to store the swapped pairs const invertedObj = {}; + // Loop through each [key, value] pair in the original object for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + // Use the VALUE as the new key, and the KEY as the new value + invertedObj[value] = key; } + // Return the inverted object return invertedObj; } -// a) What is the current return value when invert is called with { a : 1 } - -// b) What is the current return value when invert is called with { a: 1, b: 2 } - -// c) What is the target return value when invert is called with {a : 1, b: 2} - -// c) What does Object.entries return? Why is it needed in this program? - -// d) Explain why the current return value is different from the target output - -// e) Fix the implementation of invert (and write tests to prove it's fixed!) +module.exports = invert; \ No newline at end of file diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..fbca01e77 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -4,25 +4,85 @@ Write a function called countWords that - takes a string as an argument - returns an object where - - the keys are the words from the string and - - the values are the number of times the word appears in the string + - the keys are the words from the string and + - the values are the number of times the word appears in the string Example If we call countWords like this: countWords("you and me and you") then the target output is { you: 2, and: 2, me: 1 } - To complete this exercise you should understand - - Strings and string manipulation - - Loops - - Comparison inside if statements - - Setting values on an object + ## Advanced challenges -## Advanced challenges + 1. Remove all of the punctuation (e.g. ".", ",", "!", "?") to tidy up the results + 2. Ignore the case of the words to find more unique words. e.g. (A === a, Hello === hello) + 3. Order the results to find out which word is the most common in the input +*/ + +// Count how many times each word appears in a string +function countWords(str) { + // If the string is empty, return an empty object + if (str.trim().length === 0) { + return {}; + } + + // Advanced challenge 1: Remove punctuation marks from the string + const cleaned = str.replace(/[.,!?;:'"()]/g, ""); + + // Advanced challenge 2: Convert the whole string to lowercase so "Hello" and "hello" count as the same word + const lowered = cleaned.toLowerCase(); + + // Split the string into an array of individual words (split by spaces) + const words = lowered.split(" "); + + // Create an empty object to store the word counts + const counts = {}; + + // Loop through each word in the array + for (const word of words) { + // Skip empty strings that may result from extra spaces + if (word === "") { + continue; + } + + // If the word already exists in counts, add 1; otherwise start at 1 + counts[word] = (counts[word] || 0) + 1; + } + + // Advanced challenge 3: Sort words by count (most common first) + const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]); + + // Convert the sorted array back into an object + const sortedCounts = {}; + for (const [word, count] of sorted) { + sortedCounts[word] = count; + } + + return sortedCounts; +} + +// Test the function +console.log(countWords("you and me and you")); +console.log(countWords("Hello hello world! World.")); +console.log(countWords("the cat sat on the mat, the cat sat")); + +/* +## Brief Explanation + +- `str.replace(/[.,!?;:'"()]/g, "")` removes all punctuation. The `/g` flag means "find all matches, not just the first one". +- `.toLowerCase()` converts everything to lowercase so "Hello" and "hello" are treated as the same word. +- `.split(" ")` breaks the string into an array at every space. +- The `for` loop counts each word using the same pattern as the `tally` function you already completed. +- `Object.entries(counts).sort((a, b) => b[1] - a[1])` sorts the entries by count from highest to lowest. + +## How to Test + +node count-words.js -1. Remove all of the punctuation (e.g. ".", ",", "!", "?") to tidy up the results -2. Ignore the case of the words to find more unique words. e.g. (A === a, Hello === hello) +Expected output: -3. Order the results to find out which word is the most common in the input +{ you: 2, and: 2, me: 1 } +{ hello: 2, world: 2 } +{ the: 3, cat: 2, sat: 2, on: 1, mat: 1 } */ diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..559190771 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -1,5 +1,3 @@ -// You are given an implementation of calculateMode - // calculateMode's implementation can be broken down into two stages: // Stage 1. One part of the code tracks the frequency of each value @@ -8,29 +6,71 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { - // track frequency of each value +// Stage 1: Count how many times each number appears in the array +function countFrequencies(list) { + // Create a new Map to store each number and its count let freqs = new Map(); for (let num of list) { + // Skip any value that is not a number if (typeof num !== "number") { continue; } + // If the number exists in the map, add 1; otherwise start at 1 freqs.set(num, (freqs.get(num) || 0) + 1); } - // Find the value with the highest frequency + // Return the Map of frequencies + return freqs; +} + +// Stage 2: Find the number with the highest frequency +function findHighestFrequency(freqs) { let maxFreq = 0; let mode; + + // Loop through each [number, frequency] pair in the Map for (let [num, freq] of freqs) { + // If this frequency is higher than the current max, update if (freq > maxFreq) { mode = num; maxFreq = freq; } } + // If no numbers were found, return NaN; otherwise return the mode return maxFreq === 0 ? NaN : mode; } +// Main function: combine both stages +function calculateMode(list) { + // Stage 1: count frequencies + const freqs = countFrequencies(list); + + // Stage 2: find the value with the highest frequency + return findHighestFrequency(freqs); +} + module.exports = calculateMode; + +/* +## Brief Explanation + +- countFrequencies takes the array and returns a Map. A Map is like an object but it can use any type as a key (including numbers). freqs.set(num, value) sets a value, and freqs.get(num) reads it. +- findHighestFrequency loops through the Map and finds the number that appears the most. If two numbers have the same count, the one that appears first wins (because > does not include equal). +- calculateMode simply calls both functions in order — this is what "refactoring" means: splitting one big function into smaller, clearer pieces. + +## How to Test + +npx jest stretch/mode.test.js + + +Expected output: + +PASS ./mode.test.js + calculateMode() + ✓ returns the most frequent number in an array + ✓ returns the first mode in case of multiple modes + ✓ ignores non-number values +*/ \ No newline at end of file diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..345471fb2 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -4,13 +4,22 @@ // When this till object is passed to totalTill // Then it should return the total amount in pounds +// Calculate the total value of all coins in the till function totalTill(till) { + // Start with a total of 0 pence let total = 0; + // Loop through each [coin, quantity] pair in the till object for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + // Use parseInt to extract the number from strings like "50p" + // parseInt("50p") returns 50, parseInt("1p") returns 1 + const coinValue = parseInt(coin); + + // Multiply the coin value in pence by the quantity of that coin + total += coinValue * quantity; } + // Divide by 100 to convert pence to pounds and add the pound symbol return `£${total / 100}`; } @@ -20,12 +29,39 @@ const till = { "50p": 4, "20p": 10, }; + const totalAmount = totalTill(till); +console.log(totalAmount); -// a) What is the target output when totalTill is called with the till object +// a) What is the target output when totalTill is called with the till object? +// Answer: "£4.4" +// Because: (1 * 10) + (5 * 6) + (50 * 4) + (20 * 10) = 10 + 30 + 200 + 200 = 440 pence +// 440 / 100 = 4.4 pounds // b) Why do we need to use Object.entries inside the for...of loop in this function? +// Answer: Because for...of cannot loop over a plain object directly. +// Object.entries(till) converts the object into an array of [key, value] pairs +// like [["1p", 10], ["5p", 6], ["50p", 4], ["20p", 10]] +// This allows for...of to loop through each pair and destructure it into [coin, quantity]. // c) What does coin * quantity evaluate to inside the for...of loop? +// Answer: In the ORIGINAL buggy code, coin * quantity gives NaN every time. +// Because coin is a string like "1p", and "1p" * 10 is NaN. +// JavaScript cannot multiply a string containing letters by a number. +// The fix is to use parseInt(coin) which extracts just the number part. +// For example: parseInt("50p") gives 50, so 50 * 4 gives 200. // d) Write a test for this function to check it works and then fix the implementation of totalTill +// Answer: The fix is on line 17 above: using parseInt(coin) instead of coin directly. +// See till.test.js for the test file. + +/* +## How to Test + +node stretch/till.js + + +Expected output: + +£4.4 +*/