From 66e252a7e7fc9512d404f261b823f130dd700312 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Sat, 5 Jul 2025 13:06:55 +0200 Subject: [PATCH 01/16] debugged address.js --- Sprint-2/debug/address.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..b3920cf8c 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,7 +1,13 @@ // Predict and explain first... +// The code is trying to access the houseNumber property of the address object +// but it's using an incorrect syntax. Instead of using address[0], +// it should use address.houseNumber or address['houseNumber']. // This code should log out the houseNumber from the address object // but it isn't working... +// It logs `undefined` because `address[0]` is trying to access the first element +// of an array, but `address` is an object, not an array. + // Fix anything that isn't working const address = { @@ -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']}.`); From 8c18f13c8fb7b69a38d0a33d5f59a9e921663d4a Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Sat, 5 Jul 2025 13:20:26 +0200 Subject: [PATCH 02/16] debugged author.js --- Sprint-2/debug/author.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..eb8e730c7 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,7 +1,14 @@ // Predict and explain first... +// The code is trying to iterate over the `author` object using a `for...of` loop. +// However, `for...of` is designed to iterate over iterable objects like arrays or +// strings and does not work with plain objects. Therefore, it will throw a `TypeError` +// indicating that the object is not iterable. // 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 +// It isn't working because the `for...of` loop is not suitable for iterating over +// an object. Instead, we can use `Object.values()` to get an array of the +// object's values and then iterate over that array. const author = { firstName: "Zadie", @@ -11,6 +18,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.values(author)) { console.log(value); } From 8c84f589213cdb01e221cbce803a24946d0882d0 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Tue, 8 Jul 2025 19:53:44 +0200 Subject: [PATCH 03/16] debugged recipe.js --- Sprint-2/debug/recipe.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..4a596d093 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,8 +1,16 @@ // Predict and explain first... +// The code will log out the title and how many it serves, but it will not log +// the ingredients correctly. It will not log ingredients correctly because +// ${recipe} does not access the ingredients array properly. Secondly, it will +// not format the ingredients on new lines as expected. // 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? +// To fix it, we need to access the ingredients array properly and format it so +// that each ingredient appears on a new line. We can use the join method to +// convert the array into a string with each ingredient on a new line and use +// template literals to format the output correctly. const recipe = { title: "bruschetta", @@ -11,5 +19,5 @@ const recipe = { }; console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +ingredients: + ${recipe.ingredients.join('\n ')}`); From 522c54f4cebdde7a09bddf1aa218d8f8ead21ed6 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Tue, 8 Jul 2025 20:32:24 +0200 Subject: [PATCH 04/16] wrote tests for the contains function --- Sprint-2/implement/contains.test.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..a7048273f 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,38 @@ 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 on object with existing property returns true", () => { + expect(contains({ a: 1, b: 2 }, 'a')).toBe(true); +}); + +test("contains on object with non-existent property returns false", () => { + expect(contains({ a: 1, b: 2 }, 'c')).toBe(false); +}); // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("contains on empty object returns false", () => { + expect(contains({}, 'a')).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("contains on object with existing property returns true", () => { + expect(contains({ a: 1, b: 2 }, 'a')).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains on object with non-existent property returns false", () => { + expect(contains({ a: 1, b: 2 }, 'c')).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("contains on array returns false", () => { + expect(contains([1, 2, 3], '0')).toBe(false); +}); From 65595b462aca25b109f0e84a248766ce29cf44bc Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Tue, 8 Jul 2025 20:34:39 +0200 Subject: [PATCH 05/16] implemented the contains function --- Sprint-2/implement/contains.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..a7a9e2eca 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,21 @@ -function contains() {} +function contains() { + // Check if the first argument is an object + if (typeof arguments[0] !== 'object' || arguments[0] === null) { + return false; // Return false for non-object types + } + + // If the first argument is an array, return false + if (Array.isArray(arguments[0])) { + return false; + } + + // Check if the second argument is a string + if (typeof arguments[1] !== 'string') { + return false; // Return false for non-string property names + } + + // Check if the object contains the property + return Object.prototype.hasOwnProperty.call(arguments[0], arguments[1]); +} module.exports = contains; From b8e7e49e257db97cf80138197574df7f69ea435d Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Tue, 8 Jul 2025 22:15:30 +0200 Subject: [PATCH 06/16] wrote tests for the createLookup function --- Sprint-2/implement/lookup.test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..b98c05a1e 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,13 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +test("creates a country currency code lookup for multiple codes", () => { + const input = [['US', 'USD'], ['CA', 'CAD']]; + const expectedOutput = { + US: 'USD', + CA: 'CAD' + }; + expect(createLookup(input)).toEqual(expectedOutput); +}); /* From 4d5128bb054ac427f07810a49029650d6f4135ef Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Tue, 8 Jul 2025 22:17:54 +0200 Subject: [PATCH 07/16] implemented the createLookup function --- Sprint-2/implement/lookup.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..2bcbbd79b 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,9 @@ -function createLookup() { - // implementation here +function createLookup(pairs) { + const lookup = {}; + for (const [country, currency] of pairs) { + lookup[country] = currency; + } + return lookup; } module.exports = createLookup; From 826d508cd3af2dc1860672fe22da98f7e5cd067a Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Tue, 8 Jul 2025 23:11:32 +0200 Subject: [PATCH 08/16] wrote tests for the parseQueryString function --- Sprint-2/implement/querystring.test.js | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..cc7000436 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -10,3 +10,45 @@ test("parses querystring values containing =", () => { "equation": "x=y+1", }); }); + +test("parses querystring values containing multiple =", () => { + expect(parseQueryString("equation=x=y+1&another=2=3")).toEqual({ + "equation": "x=y+1", + "another": "2=3", + }); +}); + +test("parses querystring with no values", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("parses querystring with multiple values", () => { + expect(parseQueryString("a=1&b=2&c=3")).toEqual({ + "a": "1", + "b": "2", + "c": "3", + }); +}); + +test("parses querystring with empty values", () => { + expect(parseQueryString("a=&b=&c=")).toEqual({ + "a": "", + "b": "", + "c": "", + }); +}); + +test("parses querystring with special characters", () => { + expect(parseQueryString("a=1&b=2%20with%20spaces&c=3%40email.com")).toEqual({ + "a": "1", + "b": "2 with spaces", + "c": "3@email.com", + }); +}); + +test("parses querystring with encoded characters", () => { + expect(parseQueryString("a=1&b=%C3%A9%20%C3%A0%20%C3%A9")).toEqual({ + "a": "1", + "b": "é à é", + }); +}); From 535764fbca9e329f18a30b7743eb03e469617574 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Tue, 8 Jul 2025 23:16:16 +0200 Subject: [PATCH 09/16] updated the parseQueryString function --- Sprint-2/implement/querystring.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..f378571f8 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -6,8 +6,12 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + const [key, ...rest] = pair.split("="); + const value = rest.join("="); + // Decode key and value to handle special characters + const decodedKey = decodeURIComponent(key); + const decodedValue = decodeURIComponent(value); + queryParams[decodedKey] = decodedValue; } return queryParams; From c236adab7918c8e2d971b4d7baebbcac7bcdfca4 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Wed, 9 Jul 2025 21:18:37 +0200 Subject: [PATCH 10/16] wrote tests for the tally function --- Sprint-2/implement/tally.test.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..20bb65bb5 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,36 @@ const tally = require("./tally.js"); // Given a function called tally // When passed an array of items // Then it should return an object containing the count for each unique item +test("tally on an array of items returns an object with counts", () => { + const input = ['a', 'b', 'a', 'c', 'b', 'a']; + const expectedOutput = { a: 3, b: 2, c: 1 }; + const result = tally(input); + expect(result).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 on an empty array returns an empty object", () => { + const input = []; + const expectedOutput = {}; + const result = tally(input); + expect(result).toEqual(expectedOutput); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("tally on an array with duplicate items returns counts for each unique item", () => { + const input = ['a', 'b', 'a', 'c', 'b', 'a']; + const expectedOutput = { a: 3, b: 2, c: 1 }; + const result = tally(input); + expect(result).toEqual(expectedOutput); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("tally on an invalid input throws an error", () => { + expect(() => tally("invalid")).toThrowError("Invalid input"); +}); From 917f2ce13940169f2a1e05d5c18a7c84886f242f Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Wed, 9 Jul 2025 21:21:00 +0200 Subject: [PATCH 11/16] implemented the tally function --- Sprint-2/implement/tally.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..341b14067 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,15 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("Invalid input"); + } + + return items.reduce((acc, item) => { + if (typeof item !== 'string') { + throw new Error("Invalid input"); + } + acc[item] = (acc[item] || 0) + 1; + return acc; + }, {}); +} module.exports = tally; From 51c9c7248a0eb1a91c8cdf356604535b034b9263 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Thu, 10 Jul 2025 06:54:13 +0200 Subject: [PATCH 12/16] fixed the implementation of invert --- Sprint-2/interpret/invert.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..81b69c47b 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,6 +6,8 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} +/* + function invert(obj) { const invertedObj = {}; @@ -17,13 +19,40 @@ function invert(obj) { } // a) What is the current return value when invert is called with { a : 1 } +console.log(invert({ a: 1 })); // returns { key: 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } +console.log(invert({ a: 1, b: 2 })); // returns { key: 2 } + +*/ // c) What is the target return value when invert is called with {a : 1, b: 2} +// target output: {"1": "a", "2": "b"} // c) What does Object.entries return? Why is it needed in this program? +// Object.entries returns an array of a given object's own enumerable string-keyed +// property [key, value] pairs. It is needed in this program to easily iterate +// over the key-value pairs of the input object. + // 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. +// It uses `invertedObj.key = value` instead of `invertedObj[value] = key` // 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; +} + +// Tests +console.log(invert({ a: 1 })); // returns { "1": "a" } +console.log(invert({ a: 1, b: 2 })); // returns { "1": "a", "2": "b" } +console.log(invert({ x: 10, y: 20 })); // returns { "10": "x", "20": "y" } \ No newline at end of file From 86cbcab4be2a0dd6593d17cfe66aaae08bd20b67 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Thu, 10 Jul 2025 07:17:48 +0200 Subject: [PATCH 13/16] implemented the countWords function --- Sprint-2/stretch/count-words.js | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..4d3586387 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,37 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(str) { + if (typeof str !== 'string') { + throw new Error("Invalid input: expected a string"); + } + + // Remove punctuation and normalize to lowercase + const cleanedText = str + .replace(/[.,!?]/g, '') // Remove punctuation + .toLowerCase(); // Convert to lowercase + + const words = cleanedText.split(/\s+/); // Split by whitespace + const counts = {}; + + for (const word of words) { + if (word === '') continue; // Skip empty strings + counts[word] = (counts[word] || 0) + 1; + } + + // Sort by most common words + const sortedCounts = Object.fromEntries( + Object.entries(counts).sort(([, a], [, b]) => b - a) + ); + + return sortedCounts; +} + +// Test cases: + +console.log(countWords("You and me, and YOU!")); +// Output: { you: 2, and: 2, me: 1 } + +console.log(countWords("Hello, world! Hello again.")); +// Output: { hello: 2, world: 1, again: 1 } From 453bfe995b4ac675d166a33e07548f62d94dc1fd Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Thu, 10 Jul 2025 13:59:53 +0200 Subject: [PATCH 14/16] refactored calculateMode --- Sprint-2/stretch/mode.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..bde2fddcf 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -9,24 +9,29 @@ // into smaller functions using the stages above function calculateMode(list) { - // track frequency of each value - let freqs = new Map(); + const freqs = getFrequencies(list); + return findModeFromFrequencies(freqs); +} - for (let num of list) { - if (typeof num !== "number") { - continue; - } +function getFrequencies(list) { + const freqs = new Map(); + for (let 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; +} + +function findModeFromFrequencies(freqs) { let maxFreq = 0; let mode; + for (let [num, freq] of freqs) { if (freq > maxFreq) { - mode = num; maxFreq = freq; + mode = num; } } From 854532f5161e3eeda9b4206a0dc0006e1e1271e8 Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Thu, 10 Jul 2025 15:54:42 +0200 Subject: [PATCH 15/16] wrote tests for the totalTill function --- Sprint-2/stretch/till.test.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Sprint-2/stretch/till.test.js diff --git a/Sprint-2/stretch/till.test.js b/Sprint-2/stretch/till.test.js new file mode 100644 index 000000000..7e586836a --- /dev/null +++ b/Sprint-2/stretch/till.test.js @@ -0,0 +1,33 @@ +const totalTill = require("./till"); + +describe("totalTill", () => { + test("calculates the total amount in pounds for a typical till", () => { + const till = { + "1p": 10, // 10p + "5p": 6, // 30p + "50p": 4, // 200p + "20p": 10, // 200p + }; + const result = totalTill(till); + expect(result).toBe("£4.40"); + }); + + test("returns £0 when till is empty", () => { + const till = {}; + const result = totalTill(till); + expect(result).toBe("£0"); + }); + + test("works with only one coin type", () => { + const till = { "2p": 50 }; // 100p = £1 + const result = totalTill(till); + expect(result).toBe("£1"); + }); + + test("does not modify the input object", () => { + const till = { "10p": 5, "1p": 10 }; + const tillCopy = { ...till }; + totalTill(till); + expect(till).toEqual(tillCopy); + }); +}); \ No newline at end of file From 53a3392124d4ce16dde18a8bb324482a8247b1fa Mon Sep 17 00:00:00 2001 From: Malusi Skunyana Date: Thu, 10 Jul 2025 15:57:41 +0200 Subject: [PATCH 16/16] fixed the implementation of the totalTill function --- Sprint-2/stretch/till.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..cd4dae677 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -4,6 +4,7 @@ // When this till object is passed to totalTill // Then it should return the total amount in pounds +/* function totalTill(till) { let total = 0; @@ -21,11 +22,32 @@ const till = { "20p": 10, }; const totalAmount = totalTill(till); +*/ // a) What is the target output when totalTill is called with the till object +// target output: "£3.16" // b) Why do we need to use Object.entries inside the for...of loop in this function? +// We use Object.entries to get both the coin and its quantity in each iteration. +// This allows us to iterate over the key-value pairs of the till object. // c) What does coin * quantity evaluate to inside the for...of loop? +// Coin * quantity evaluates to the total value of that particular 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", ""), 10); + total += coinValue * quantity; + } + + const pounds = total / 100; + const formatted = pounds % 1 === 0 ? `£${pounds}` : `£${pounds.toFixed(2)}`; + + return formatted; +} + +module.exports = totalTill;