diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 255490743..000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "recommendations": [ - "esbenp.prettier-vscode", - "dbaeumer.vscode-eslint", - "streetsidesoftware.code-spell-checker", - "eamodio.gitlens", - "ritwickdey.LiveServer", - "vsliveshare.vsliveshare" - ] -} diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..a8e3fd8a5 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,4 +1,10 @@ // Predict and explain first... +// This code is intended to log the house number from an address object. +// However, it seems that the console log is not functioning as expected and the result is undefined. +// The issue lies in the way the property is being accessed from the address object. +// to fix this, we need to access the property using the correct key. + + // This code should log out the houseNumber from the address object // but it isn't working... @@ -12,4 +18,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..c4a8c06b0 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,4 +1,9 @@ // Predict and explain first... +// This code is intended to log out all the property values in the `author` object. +// However, it is not working because the `for...of` loop is designed to iterate +// over iterable objects like arrays or strings, but the `author` object is a plain object +// which is not iterable. To fix this, we can use `Object.values()` to get +// an array of the object's values, which can then be iterated over with a `for...of` loop. // 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 @@ -11,6 +16,7 @@ const author = { alive: true, }; -for (const value of author) { +for (const key in author) { + const value = author[key]; console.log(value); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..a7a8811cc 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,4 +1,9 @@ // Predict and explain first... +// This code is intended to log out the title, how many it serves, and the ingredients of a recipe. +// However, it is not working correctly because the `console.log` statement is trying to log +// the entire `recipe` object directly, which will not format the ingredients as expected. +// Instead, we need to iterate over the `ingredients` array and log each ingredient on a new line. +// To fix this, we can use a template literal to format the output and use `join` to create a string of ingredients separated by new lines. // This program should log out the title, how many it serves and the ingredients. // Each ingredient should be logged on a new line @@ -10,6 +15,6 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +console.log(`${recipe.title} serves ${recipe.serves} +ingredients: +${recipe.ingredients.join('\n')}`); diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..29d4115f6 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,13 @@ -function contains() {} +function contains(object, property) { + const keysArray = Object.keys(object); // Converts object keys to an array + + for (let i = 0; i < keysArray.length; i++) { + if (keysArray[i] === property) { + return true; + } + } + + return false; // Property wasn't found + } module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..90ec56fe2 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,41 @@ as the object doesn't contains a key of 'c' // Given a contains function // When passed an object and a property name // Then it should return true if the object contains the property, false otherwise +test("contains returns true for existing property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, 'a')).toBe(true); +}); // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); - +test("contains returns false for empty object", () => { + const obj = {}; + expect(contains(obj, 'a')).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("contains returns true for existing property in object", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, 'b')).toBe(true); +}); + // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains returns false for non-existent property in object", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, 'c')).toBe(false); +}); + // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("contains returns false for invalid parameters", () => { + const arr = [1, 2, 3]; + expect(contains(arr, 'length')).toBe(false); +}); + diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..3a73d7d93 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,15 @@ -function createLookup() { - // implementation here +function createLookup(pairs) { + const result = {}; + for (const [country, currency] of pairs) { + result[country] = currency; + } + return result; } -module.exports = createLookup; + +// Example usage: + //const countryCurrencyPairs = [['US', 'USD'], ['CA', 'CAD']]; + //const lookup = createLookup([['US', 'USD'],['CA', 'CAD']]); + //console.log(lookup); // { US: 'USD', CA: 'CAD' } + + module.exports = createLookup; \ No newline at end of file diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..9145761fd 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,7 +1,13 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); - +test("createLookup returns an object with country codes as keys and currency codes as values", () => { + const countryCurrencyPairs = [['US', 'USD'], ['CA', 'CAD']]; + const expectedLookup = { + 'US': 'USD', + 'CA': 'CAD' + }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); +}); /* Create a lookup object of key value pairs from an array of code pairs diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..5e00686cc 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,16 +1,26 @@ function parseQueryString(queryString) { - const queryParams = {}; - if (queryString.length === 0) { - return queryParams; - } - const keyValuePairs = queryString.split("&"); + if (!queryString) return {}; + + const result = {}; + const pairs = queryString.split("&"); + + for (const pair of pairs) { + const index = pair.indexOf("="); + let key, value; - for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + if (index === -1) { + key = decodeURIComponent(pair); + value = ""; + } else { + key = decodeURIComponent(pair.slice(0, index)); + value = decodeURIComponent(pair.slice(index + 1)); + } + + result[key] = value; } - return queryParams; + return result; } + module.exports = parseQueryString; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..1985bd139 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -10,3 +10,25 @@ test("parses querystring values containing =", () => { "equation": "x=y+1", }); }); + +test("parses querystring with multiple values for the same key", () => { + expect(parseQueryString("name=John&name=Jane")).toEqual({ + "name": "Jane", + }); +}); + +test("parses querystring with empty values", () => { + expect(parseQueryString("name=&age=&city=")).toEqual({ + "name": "", + "age": "", + "city": "", + }); +}); + +test("parses querystring with only keys", () => { + expect(parseQueryString("name=&age&city")).toEqual({ + "name": "", + "age": "", + "city": "", + }); +}); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..eb947dfe0 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,14 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("Input must be an array"); + } + + const counts = {}; + for (const item of items) { + counts[item] = (counts[item] || 0) + 1; + } + + return counts; + } module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..999389d43 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -20,15 +20,42 @@ const tally = require("./tally.js"); // When passed an array of items // Then it should return an object containing the count for each unique item +test("tally returns counts for each unique item", () => { + const items = ['a', 'b', 'a', 'c', 'b', 'a']; + const expectedOutput = { a: 3, b: 2, c: 1 }; + expect(tally(items)).toEqual(expectedOutput); +}); // Given an empty array // When passed to tally // Then it should return an empty object -test.todo("tally on an empty array returns an empty object"); - +test("tally returns empty object for empty array", () => { + const items = []; + expect(tally(items)).toEqual({}); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("tally returns counts for duplicate items", () => { + const items = ['x', 'y', 'x', 'z', 'y', 'x']; + const expectedOutput = { x: 3, y: 2, z: 1 }; + expect(tally(items)).toEqual(expectedOutput); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("tally throws error for invalid input", () => { + expect(() => tally("invalid")).toThrow("Input must be an array"); +}); + +test("tally throws for number input", () => { + expect(() => tally(123)).toThrow("Input must be an array"); + }); + + test("tally throws for object input", () => { + expect(() => tally({ a: 1 })).toThrow("Input must be an array"); + }); + + test("tally throws for null input", () => { + expect(() => tally(null)).toThrow("Input must be an array"); + }); \ No newline at end of file diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..1d40ca689 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -16,14 +16,30 @@ function invert(obj) { return invertedObj; } + // a) What is the current return value when invert is called with { a : 1 } +// The current return value when invert is called with { a: 1 } is { key: 1 }. // b) What is the current return value when invert is called with { a: 1, b: 2 } +// The current return value when invert is called with { a: 1, b: 2 } is { key: 2 }. // c) What is the target return value when invert is called with {a : 1, b: 2} +// The target return value when invert is called with { a: 1, b: 2 } should be { "1": "a", "2": "b" }. // c) What does Object.entries return? Why is it needed in this program? +// Object.entries returns an array of key-value pairs from the object. +// It is needed in this program to iterate over the object's properties and their corresponding values. // d) Explain why the current return value is different from the target output +// The current return value is different from the target output because the code incorrectly assigns the value to the key in the inverted object. // e) Fix the implementation of invert (and write tests to prove it's fixed!) +function invert(obj) { + const invertedObj = {}; + + for (const [key, value] of Object.entries(obj)) { + invertedObj[value] = key; + } + + return invertedObj; +} diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..3458e19cc 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,51 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(str) { + // Split the string into words + const words = str.split(/\s+/); + + // Create an object to hold the word counts + const wordCount = {}; + + // Loop through each word + for (const word of words) { + // Normalize the word to lowercase and remove punctuation + const cleanWord = word.toLowerCase().replace(/[.,!?]/g, ''); + + // If the word is not in the object, add it with a count of 1 + if (!wordCount[cleanWord]) { + wordCount[cleanWord] = 1; + } else { + // If it exists, increment the count + wordCount[cleanWord]++; + } + } + + return wordCount; +} + +//function countWords(str) { +// const words = str.split(/\s+/); +// const counts = {}; + +// for (let word of words) { +// word = word.toLowerCase().replace(/[.,!?]/g, ''); +// counts[word] = (counts[word] || 0) + 1; +// } + +// return counts; +// } + +// Example usage +const result = countWords("you and me and you"); +console.log(result); // { you: 2, and: 2, me: 1 } + +console.log(countWords("Hello, hello! How are you? You are great.")); +// { hello: 2, how: 1, are: 2, you: 2, great: 1 } +console.log(countWords("This is a test. This test is only a test.")); +// { this: 2, is: 3, a: 2, test: 3, only: 1 } +// Example with punctuation and case insensitivity +console.log(countWords("Hello, world! Hello, everyone. Hello!")); +// { hello: 3, world: 1, everyone: 1 } \ No newline at end of file diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..c2bae11ae 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,29 +8,41 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { - // track frequency of each value - let freqs = new Map(); +function trackFrequency(list) { + if (!Array.isArray(list)) { + throw new Error("Input must be an array"); + } - for (let num of list) { + const freqs = new Map(); + + for (const num of list) { if (typeof num !== "number") { continue; } - freqs.set(num, (freqs.get(num) || 0) + 1); } - // Find the value with the highest frequency + return freqs; +} + +// Stage 2: Find the value with the highest frequency +function highestFrequency(freqs) { let maxFreq = 0; let mode; - for (let [num, freq] of freqs) { + + for (const [num, freq] of freqs) { if (freq > maxFreq) { - mode = num; maxFreq = freq; + mode = num; } } - return maxFreq === 0 ? NaN : mode; } -module.exports = calculateMode; +// Main function: combine both stages +function calculateMode(list) { + const freqs = trackFrequency(list); + return highestFrequency(freqs); +} + +module.exports = calculateMode; \ No newline at end of file diff --git a/Sprint-2/stretch/mode.test.js b/Sprint-2/stretch/mode.test.js index ca33c28a3..f85c5a127 100644 --- a/Sprint-2/stretch/mode.test.js +++ b/Sprint-2/stretch/mode.test.js @@ -29,4 +29,11 @@ describe("calculateMode()", () => { expect(calculateMode(nums)).toEqual(3); }); + + test("returns NaN for an empty array", () => { + const nums = []; + + expect(calculateMode(nums)).toBeNaN(); + }); + }); diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..8e89d8977 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -23,9 +23,31 @@ const till = { const totalAmount = totalTill(till); // a) What is the target output when totalTill is called with the till object +// The target output when totalTill is called with the till object is "£3.16". +// The current output is "£0.00" because the implementation is incorrect. + // b) Why do we need to use Object.entries inside the for...of loop in this function? +// We need to use Object.entries to iterate over the key-value pairs of the till object. +// It allows us to access both the coin (key) and its quantity (value) in each iteration. +// e.g. Object.entries(till) returns [["1p", 10], ["5p", 6], ["50p", 4], ["20p", 10]]. + // c) What does coin * quantity evaluate to inside the for...of loop? +// Inside the for...of loop, coin * quantity evaluates to the total value of that type of coin in pence. +// For example, if coin is "1p" and quantity is 10, it evaluates +// to 1 * 10 = 10 pence. +// It evaluates to the total value of that type of coin in pence. + // d) Write a test for this function to check it works and then fix the implementation of totalTill +function totalTill(till) { + let total = 0; + + for (const [coin, quantity] of Object.entries(till)) { + const coinValue = parseInt(coin.replace('p', '')); // Convert coin string to number + total += coinValue * quantity; // Calculate total in pence + } + + return `£${(total / 100).toFixed(2)}`; // Return total in pounds formatted to two decimal places +}