From b8e5f68bddbaae8eff3daad5036332413cf61bc6 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Mon, 13 Oct 2025 16:21:09 +0100 Subject: [PATCH 01/14] fixed bug in address.js --- Sprint-2/debug/address.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..7ddb854b9 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,9 @@ // Predict and explain first... +// I guess the bug could be related to how to access property in an object. +// address[0] may not be able to access houseNumber as a key +// if we change address[0] to address['houseNumber'] or address.houseNumber, it may resolve the problem. + // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -12,4 +16,7 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); + +// After trying both address["houseNumber"] and address.houseNumber, +// console.log can successfully print out the statement with right number accordingly. From bfe0b62ef026c8a58af085bb128a8c55679f7a67 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Mon, 13 Oct 2025 16:37:59 +0100 Subject: [PATCH 02/14] getting all the values in object author --- Sprint-2/debug/author.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..06360ed08 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,7 @@ // Predict and explain first... +// I guess the problem could be induced by console.log(value), as each value is a key: value pair, but we only need the values. + // 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 +13,9 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); -} +console.log(Object.values(author)); + +// After running the code, I got TypeError: author is not iterable +// so, the actual problem is an object cannot be iterated in for loop. +// To print out all the values in author, I attempted Object.values(author) +// which return all the values in an array [ 'Zadie', 'Smith', 'writer', 40, true ] From de5e07ac12ef4717acf9f35cd92489c077e9b86f Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Mon, 13 Oct 2025 16:57:32 +0100 Subject: [PATCH 03/14] fixed bug in recipe --- Sprint-2/debug/recipe.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..1e10c475b 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,9 @@ // Predict and explain first... +// For the first glance, I guess I have to do a looping in recipe.ingredients +// Before any changes, ingredients: ${recipe} would be print out the whole object. +// for print out each ingredient on a new line, I may have to use \n + // 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? @@ -12,4 +16,7 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.map((ingredient) => `${ingredient}`).join("\n")}`); + +// After the first attempt, I realized that I can't use a for loop inside ${} +// so, I tried map function to get each ingredient and added \n between them. From 612434333f863edfa0f549a2ba10a6633fc89100 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Tue, 14 Oct 2025 10:09:42 +0100 Subject: [PATCH 04/14] completed contains tests and function --- Sprint-2/implement/contains.js | 7 ++++++- Sprint-2/implement/contains.test.js | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..5784f3afb 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(obj, targetProperty) { + if (typeof obj !== "object" || Array.isArray(obj) || obj === null) { + throw new Error("Input should be an object"); + } + return Object.keys(obj).includes(targetProperty); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..bb604827e 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,35 @@ as the object doesn't contains a key of 'c' // 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", () => { + const emptyObject = {}; + expect(contains(emptyObject, "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 an object with properties, passed an existing property name, returns true", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "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 an object properties, passed a non-existing property name, returns false", () => { + 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("Given invalid parameters returns an error", () => { + const errorMessage = "Input should be an object"; + expect(() => contains([{ a: 1 }, { b: 2 }], "a")).toThrow(errorMessage); + expect(() => contains(null, "a")).toThrow(errorMessage); + expect(() => contains(undefined, "a")).toThrow(errorMessage); + expect(() => contains(true, "a")).toThrow(errorMessage); + expect(() => contains(false, "a")).toThrow(errorMessage); +}); From 9eaf9c814abf2a4556fb44adc73a094de26091ca Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Tue, 14 Oct 2025 14:12:40 +0100 Subject: [PATCH 05/14] completed lookup --- Sprint-2/implement/lookup.js | 15 ++++++- Sprint-2/implement/lookup.test.js | 69 ++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..b55c808e5 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,16 @@ -function createLookup() { - // implementation here +function createLookup(arr) { + if (!Array.isArray(arr)) { + throw new Error("Input should be an array of arrays of code pairs"); + } + + const validArray = arr.every( + (element) => Array.isArray(element) && element.length === 2 + ); + if (!validArray) { + throw new Error("Input should be an array of arrays of code pairs"); + } + + return Object.fromEntries(arr); } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..f297287be 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,73 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +const errorMessage = "Input should be an array of arrays of code pairs"; + +test("Given an empty array, returns an empty object", () => { + expect(createLookup([])).toStrictEqual({}); +}); + +test("Given invalid parameter, return error message", () => { + expect(() => createLookup("Invalid input")).toThrow(errorMessage); + expect(() => createLookup({ a: "b" })).toThrow(errorMessage); + expect(() => createLookup(123)).toThrow(errorMessage); + expect(() => createLookup(null)).toThrow(errorMessage); + expect(() => createLookup(undefined)).toThrow(errorMessage); + expect(() => createLookup(true)).toThrow(errorMessage); +}); + +test("creates a country currency code lookup for multiple codes", () => { + const validInput = [ + ["US", "USD"], + ["CA", "CAD"], + ["JP", "JPY"], + ]; + const target = { + US: "USD", + CA: "CAD", + JP: "JPY", + }; + expect(createLookup(validInput)).toStrictEqual(target); +}); + +test("Given an array with empty arrays, returns error message", () => { + const emptyArrays = [[], []]; + expect(() => createLookup(emptyArrays)).toThrow(errorMessage); + const emptyArrays2 = [[], [], ["JP", "JPY"]]; + expect(() => createLookup(emptyArrays2)).toThrow(errorMessage); +}); + +test("Given an array with objects, return error message", () => { + const objectsArray = [{ a: "b" }, { c: "d" }]; + expect(() => createLookup(objectsArray)).toThrow(errorMessage); +}); + +test("Given an array mixed with valid arrays and invalid input, returns error message", () => { + const mixInput = [["US", "USD"], { CA: "CAD" }, ["JP", "JPY"]]; + expect(() => createLookup(mixInput).toThrow(errorMessage)); + const mixInput2 = [["US", "USD"], undefined, ["JP", "JPY"]]; + expect(() => createLookup(mixInput2).toThrow(errorMessage)); + const mixInput3 = [["US", "USD"], null, ["JP", "JPY"]]; + expect(() => createLookup(mixInput3).toThrow(errorMessage)); +}); + +test("Given an array with arrays but some are displaced, return error message", () => { + const missFormatInput = [["US", "USD"], ["CA"], ["JP", "JPY", "CAD"]]; + expect(() => createLookup(missFormatInput)).toThrow(errorMessage); +}); + +test("Given an array with array of non-strings, return accordingly", () => { + const nonStringInput = [ + ["US", undefined], + ["CA", "CAD"], + ["JP", null], + ]; + const target = { + US: undefined, + CA: "CAD", + JP: null, + }; + expect(createLookup(nonStringInput)).toStrictEqual(target); +}); /* From d91f6e70f9783cba057a09f1dcf70f40f26d6333 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Tue, 14 Oct 2025 14:16:54 +0100 Subject: [PATCH 06/14] updated test in lookup --- Sprint-2/implement/lookup.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index f297287be..c3b505879 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -57,14 +57,16 @@ test("Given an array with arrays but some are displaced, return error message", test("Given an array with array of non-strings, return accordingly", () => { const nonStringInput = [ - ["US", undefined], + [undefined, true], ["CA", "CAD"], ["JP", null], + [123, 456], ]; const target = { - US: undefined, + undefined: true, CA: "CAD", JP: null, + 123: 456, }; expect(createLookup(nonStringInput)).toStrictEqual(target); }); From 5f097e40ecabd4f0e0c375d687dca83d4c3e7ed5 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Wed, 15 Oct 2025 16:12:55 +0100 Subject: [PATCH 07/14] added cases for querystring test and refactored the function --- Sprint-2/implement/querystring.js | 22 +++++++++++++- Sprint-2/implement/querystring.test.js | 42 ++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..305b3070b 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -3,14 +3,34 @@ function parseQueryString(queryString) { if (queryString.length === 0) { return queryParams; } + + if (queryString[0] === "?") { + queryString = queryString.slice(1); + } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + if (!pair) continue; + + const indexToSlice = pair.indexOf("="); + + let key; + let value; + + if (indexToSlice === -1) { + key = pair; + value = ""; + } else { + key = decodeURIComponent(pair.slice(0, indexToSlice)); + value = decodeURIComponent(pair.slice(indexToSlice + 1)); + } queryParams[key] = value; } return queryParams; } +parseQueryString("aloneKey"); + module.exports = parseQueryString; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..188eac8a6 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,48 @@ // Below is one test case for an edge case the implementation doesn't handle well. // Fix the implementation for this test, and try to think of as many other edge cases as possible - write tests and fix those too. -const parseQueryString = require("./querystring.js") +const parseQueryString = require("./querystring.js"); test("parses querystring values containing =", () => { expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", + equation: "x=y+1", + }); +}); + +test("Single Key with No Value", () => { + expect(parseQueryString("key=")).toEqual({ key: "" }); +}); + +test("Key Without = Sign", () => { + expect(parseQueryString("aloneKey")).toEqual({ aloneKey: "" }); +}); + +test("Multiple Parameters", () => { + expect(parseQueryString("name=Brian&age=35&country=UK")).toEqual({ + name: "Brian", + age: "35", + country: "UK", + }); +}); + +test("Duplicate Keys", () => { + expect(parseQueryString("tag=js&tag=node&tag=react")).toEqual({ + tag: "react", + }); +}); + +test("Values Containing Special Characters", () => { + expect(parseQueryString("query=a%20b%26c%3Dd")).toEqual({ query: "a b&c=d" }); + expect(parseQueryString("a=1&b=2&")).toEqual({ a: "1", b: "2" }); +}); + +test("Starting with ?", () => { + expect(parseQueryString("?foo=bar")).toEqual({ foo: "bar" }); +}); + +test("Mixed encoded and plain parts", () => { + expect(parseQueryString("message=Hello%20World%21&debug=true")).toEqual({ + message: "Hello World!", + debug: "true", }); }); From 8fa0a48071467dfaa43a900315345d48d5a9d958 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Wed, 15 Oct 2025 17:31:04 +0100 Subject: [PATCH 08/14] added edge cases for tally and refactored its function --- Sprint-2/implement/tally.js | 23 +++++++++++- Sprint-2/implement/tally.test.js | 61 +++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..d74adf26e 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,24 @@ -function tally() {} +function tally(arr) { + if (!Array.isArray(arr)) { + throw new Error("Input should be an array"); + } + if (arr.length === 0) { + return {}; + } + + arr = arr.flat(); + + let result = {}; + for (element of arr) { + const key = + typeof element === "object" ? JSON.stringify(element) : String(element); + + result[key] = (result[key] || 0) + 1; + } + + return result; +} + +tally(["a", "a", "a"]); module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..c800e02fe 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,71 @@ const tally = require("./tally.js"); // 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", () => { + expect(tally([])).toEqual({}); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("Given an array with duplicate item, it should return counts for each unique item", () => { + expect(tally(["a", "a", "a"])).toEqual({ a: 3 }); + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 }); + expect(tally(["a", "a", "b", "c", "a", "a", "b", "c"])).toEqual({ + a: 4, + b: 2, + c: 2, + }); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +const errorMessage = "Input should be an array"; +test("Given a non array input, it should return an error message", () => { + expect(() => tally({ a: "b" })).toThrow(errorMessage); + expect(() => tally("a")).toThrow(errorMessage); + expect(() => tally(123)).toThrow(errorMessage); + expect(() => tally(null)).toThrow(errorMessage); + expect(() => tally(undefined)).toThrow(errorMessage); + expect(() => tally(true)).toThrow(errorMessage); +}); + +test("Given an array with numbers, it should return counts for each number", () => { + expect(tally([1, 1, 1])).toEqual({ 1: 3 }); + expect(tally([12, 12, 12])).toEqual({ 12: 3 }); + expect(tally([1, 12, 123])).toEqual({ 1: 1, 12: 1, 123: 1 }); + expect(tally([1, 12, 123, 1, 12, 123])).toEqual({ 1: 2, 12: 2, 123: 2 }); +}); + +test("Given an array with mixed items, it should return counts for each item", () => { + expect( + tally(["a", 1, true, null, undefined, "a", 1, true, null, undefined]) + ).toEqual({ + a: 2, + 1: 2, + true: 2, + null: 2, + undefined: 2, + }); +}); + +test("Given an array with arrays, it should return counts for each item", () => { + expect(tally([1, [1, 1]])).toEqual({ 1: 3 }); + expect(tally(["a", ["a", "a"]])).toEqual({ a: 3 }); +}); + +test("Given an array with objects, it should return counts for each object", () => { + expect( + tally([ + { a: 1, b: 2, c: 3 }, + { a: 1, b: 2, c: 3 }, + ]) + ).toEqual({ '{"a":1,"b":2,"c":3}': 2 }); +}); + +test("Given an array mixed with arrays and objects, it should return counts for each item", () => { + expect( + tally([{ a: 1, b: 2, c: 3 }, "a", { a: 1, b: 2, c: 3 }, ["a", "a"]]) + ).toEqual({ '{"a":1,"b":2,"c":3}': 2, a: 3 }); +}); From 3758f52b6daf60cfb9822b88f7cd421242fabf87 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Thu, 16 Oct 2025 13:39:28 +0100 Subject: [PATCH 09/14] completed invert tests and its function --- Sprint-2/implement/querystring.js | 2 -- Sprint-2/implement/tally.js | 2 -- Sprint-2/interpret/invert.js | 33 ++++++++++++++++++++++++-- Sprint-2/interpret/invert.test.js | 39 +++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 Sprint-2/interpret/invert.test.js diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 305b3070b..5159e1493 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -31,6 +31,4 @@ function parseQueryString(queryString) { return queryParams; } -parseQueryString("aloneKey"); - module.exports = parseQueryString; diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index d74adf26e..1517c3555 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -19,6 +19,4 @@ function tally(arr) { return result; } -tally(["a", "a", "a"]); - module.exports = tally; diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..9fa0a274f 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -7,23 +7,52 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} function invert(obj) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + throw new Error("Input should be an object"); + } + const invertedObj = {}; - for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + for (let [key, value] of Object.entries(obj)) { + if (typeof key === "object") { + key = JSON.stringify(key); + } + if (typeof value === "object") { + value = JSON.stringify(value); + } + invertedObj[value] = key; } return invertedObj; } +console.log(invert({ a: 1, b: 2 })); + +module.exports = invert; + // a) What is the current return value when invert is called with { a : 1 } +// When invert is called with { a: 1 }, it returns { key: 1 } +// I found the way of assigning key and value is currently not right. // b) What is the current return value when invert is called with { a: 1, b: 2 } +// The current return is { key: 2 } +// The value of key changed from 1 to 2, when it is reassigned again in the for loop // c) What is the target return value when invert is called with {a : 1, b: 2} +// The target return value was supposed to be { '1': 'a', '2': 'b' } +// as the original key and value is swapped // c) What does Object.entries return? Why is it needed in this program? +// Object.entries return an array of arrays of each pair of key and value like [['a', '1'], ['b', '2']] +// if Object.entries({ a: 1, b: 2 }) is called +// We need it because we need to take each pair of key and value [key, value] in array like ['a', '1'] +// and swap them in the next step of the for loop // d) Explain why the current return value is different from the target output +// The current way of assigning key and value invertedObj.key = value; +// can't actually access the referencing value of key, which is 'a' +// In fact, it created a new key named 'key', rather than 'a' +// Besides, as we want to swap the key: value to value: key +// we should refactor the assigning to invertedObj[value] = key // e) Fix the implementation of invert (and write tests to prove it's fixed!) diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..b1a1da923 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,39 @@ +const invert = require("./invert"); + +describe("testing invert function", () => { + const errorMessage = "Input should be an object"; + it("should throw error message when input is not an object", () => { + expect(() => invert("abc")).toThrow(errorMessage); + expect(() => invert(123)).toThrow(errorMessage); + expect(() => invert(null)).toThrow(errorMessage); + expect(() => invert(undefined)).toThrow(errorMessage); + expect(() => invert(true)).toThrow(errorMessage); + expect(() => invert(["a", "1"])).toThrow(errorMessage); + }); + + it("should return a swapped object when an object is passed in the function", () => { + expect(invert({ a: "1" })).toStrictEqual({ 1: "a" }); + expect(invert({ a: 1, b: 2, c: 3 })).toStrictEqual({ + 1: "a", + 2: "b", + 3: "c", + }); + expect(invert({ a: null, b: undefined, c: true })).toStrictEqual({ + null: "a", + undefined: "b", + true: "c", + }); + expect(invert({ a: [1], b: [2] })).toStrictEqual({ + "[1]": "a", + "[2]": "b", + }); + expect(invert({ a: { b: 1 }, c: { d: 2 } })).toStrictEqual({ + '{"b":1}': "a", + '{"d":2}': "c", + }); + expect(invert({ "{a: b}": 1, "{c: d}": 2 })).toStrictEqual({ + 1: "{a: b}", + 2: "{c: d}", + }); + }); +}); From 5c349f2b13082184e6e11c241e457a7e2d1acb26 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sun, 19 Oct 2025 16:35:09 +0100 Subject: [PATCH 10/14] completed count-word tests and function --- Sprint-2/interpret/invert.js | 2 - Sprint-2/stretch/count-word.test.js | 115 ++++++++++++++++++++++++++++ Sprint-2/stretch/count-words.js | 20 +++++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 Sprint-2/stretch/count-word.test.js diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index 9fa0a274f..8b0cbec13 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -26,8 +26,6 @@ function invert(obj) { return invertedObj; } -console.log(invert({ a: 1, b: 2 })); - module.exports = invert; // a) What is the current return value when invert is called with { a : 1 } diff --git a/Sprint-2/stretch/count-word.test.js b/Sprint-2/stretch/count-word.test.js new file mode 100644 index 000000000..30434408b --- /dev/null +++ b/Sprint-2/stretch/count-word.test.js @@ -0,0 +1,115 @@ +const countWords = require("./count-words"); + +test("Given an empty string, it should return an empty object", () => { + expect(countWords("")).toStrictEqual({}); +}); + +test("Given an non string input, it should throw an error message", () => { + const errorMessage = "Input should be a string"; + expect(() => countWords(["non string"])).toThrow(errorMessage); + expect(() => countWords({ a: 1 })).toThrow(errorMessage); + expect(() => countWords(123)).toThrow(errorMessage); + expect(() => countWords(null)).toThrow(errorMessage); + expect(() => countWords(undefined)).toThrow(errorMessage); + expect(() => countWords(true)).toThrow(errorMessage); +}); + +test("Given a valid string, it should return an object with proper count on each word", () => { + expect(countWords("you and me and you")).toStrictEqual({ + you: 2, + and: 2, + me: 1, + }); + expect( + countWords( + "The government has announced plans to train and recruit more workers for the UK's clean energy sector" + ) + ).toStrictEqual({ + the: 2, + government: 1, + has: 1, + announced: 1, + plans: 1, + to: 1, + train: 1, + and: 1, + recruit: 1, + more: 1, + workers: 1, + for: 1, + "uk's": 1, + clean: 1, + energy: 1, + sector: 1, + }); +}); + +test("Given a valid string with punctuation, it should count each word without punctuation", () => { + expect( + JSON.stringify( + countWords( + "The government has announced plans to train and recruit more workers for the UK's clean energy sector, promising to create 400,000 extra jobs by 2030." + ) + ) + ).toStrictEqual( + JSON.stringify({ + the: 2, + to: 2, + government: 1, + has: 1, + announced: 1, + plans: 1, + train: 1, + and: 1, + recruit: 1, + more: 1, + workers: 1, + for: 1, + "uk's": 1, + clean: 1, + energy: 1, + sector: 1, + promising: 1, + create: 1, + 400000: 1, + extra: 1, + jobs: 1, + by: 1, + 2030: 1, + }) + ); + expect( + countWords( + 'Plumbers, electricians and welders are among 31 priority occupations that are "particularly in demand", with employment in renewable, wind, solar and nuclear expected to double to 860,000 in five years, ministers have said.' + ) + ).toStrictEqual({ + 31: 1, + 860000: 1, + in: 3, + and: 2, + are: 2, + to: 2, + plumbers: 1, + electricians: 1, + welders: 1, + among: 1, + priority: 1, + occupations: 1, + that: 1, + particularly: 1, + demand: 1, + with: 1, + employment: 1, + renewable: 1, + wind: 1, + solar: 1, + nuclear: 1, + expected: 1, + double: 1, + five: 1, + years: 1, + ministers: 1, + have: 1, + said: 1, + }); +}); diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..8fecd4d77 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,23 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(string) { + if (typeof string !== "string") { + throw new Error("Input should be a string"); + } + if (string === "") return {}; + const strArray = string + .replace(/[\.\,\!\?\"]/g, "") + .toLowerCase() + .split(" "); + + let result = {}; + + for (element of strArray) { + result[element] = (result[element] || 0) + 1; + } + return Object.fromEntries(Object.entries(result).sort((a, b) => b[1] - a[1])); +} + +module.exports = countWords; From cd89aa1472f5545fa6ea56d31996bf61cc06fba9 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sun, 19 Oct 2025 17:29:43 +0100 Subject: [PATCH 11/14] refactored mode's function --- Sprint-2/stretch/mode.js | 23 ++++++++++++++++------- Sprint-2/stretch/mode.test.js | 12 ++++++------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..1ad6ed0a1 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,29 +8,38 @@ // 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) { + let frequency = new Map(); for (let num of list) { if (typeof num !== "number") { continue; } - freqs.set(num, (freqs.get(num) || 0) + 1); + frequency.set(num, (frequency.get(num) || 0) + 1); } + return frequency; +} - // Find the value with the highest frequency +function highestFrequency(frequencyArray) { let maxFreq = 0; let mode; - for (let [num, freq] of freqs) { + for (let [num, freq] of frequencyArray) { if (freq > maxFreq) { mode = num; maxFreq = freq; } } - return maxFreq === 0 ? NaN : mode; } +function calculateMode(list) { + // track frequency of each value + const frequencyArray = trackFrequency(list); + + // Find the value with the highest frequency + + return highestFrequency(frequencyArray); +} + module.exports = calculateMode; diff --git a/Sprint-2/stretch/mode.test.js b/Sprint-2/stretch/mode.test.js index ca33c28a3..7d233b801 100644 --- a/Sprint-2/stretch/mode.test.js +++ b/Sprint-2/stretch/mode.test.js @@ -13,20 +13,20 @@ const calculateMode = require("./mode.js"); describe("calculateMode()", () => { test("returns the most frequent number in an array", () => { - const nums = [2, 4, 1, 2, 3, 2, 1]; + const numArray = [2, 4, 1, 2, 3, 2, 1]; - expect(calculateMode(nums)).toEqual(2); + expect(calculateMode(numArray)).toEqual(2); }); test("returns the first mode in case of multiple modes", () => { - const nums = [1, 2, 2, 3, 3]; + const numArray = [1, 2, 2, 3, 3]; - expect(calculateMode(nums)).toEqual(2); + expect(calculateMode(numArray)).toEqual(2); }); test("ignores non-number values", () => { - const nums = [1, 3, "2", 2, 3, null]; + const numArray = [1, 3, "2", 2, 3, null]; - expect(calculateMode(nums)).toEqual(3); + expect(calculateMode(numArray)).toEqual(3); }); }); From 4a4cd94c56a22ab72beed44288fa24dc6c078848 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Sun, 19 Oct 2025 18:25:33 +0100 Subject: [PATCH 12/14] completed till tests and function --- Sprint-2/stretch/till.js | 23 +++++++++++++++-- Sprint-2/stretch/till.test.js | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 Sprint-2/stretch/till.test.js diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..beb677f44 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -5,13 +5,24 @@ // Then it should return the total amount in pounds function totalTill(till) { + if (typeof till !== "object" || Array.isArray(till) || till === null) { + throw new Error("Input should be an object"); + } + + const validPence = ["1p", "5p", "20p", "50p"]; + let total = 0; + console.log(Object.entries(till)); + for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + if (validPence.indexOf(coin) === -1) { + continue; + } + total += Number(coin.slice(0, coin.length - 1)) * quantity; } - return `£${total / 100}`; + return `£${Math.floor(total / 100)}.${String(total % 100).padEnd(2, 0)}`; } const till = { @@ -22,10 +33,18 @@ const till = { }; const totalAmount = totalTill(till); +module.exports = totalTill; + // a) What is the target output when totalTill is called with the till object +// The target output is a string that state the total value of pound after converted from pence +// For example: '£4.40' // b) Why do we need to use Object.entries inside the for...of loop in this function? +// We need to convert the object till into an array of arrays [ [ '1p', 10 ], [ '5p', 6 ], [ '50p', 4 ], [ '20p', 10 ] ] +// That will enable the loop process in the for loop. // c) What does coin * quantity evaluate to inside the for...of loop? +// coin * quantity was trying to get the value of each group of pence and add to the total +// However, coin is referencing to first element of each sub array, which is a string that can't do math operation // d) Write a test for this function to check it works and then fix the implementation of totalTill diff --git a/Sprint-2/stretch/till.test.js b/Sprint-2/stretch/till.test.js new file mode 100644 index 000000000..ba22de203 --- /dev/null +++ b/Sprint-2/stretch/till.test.js @@ -0,0 +1,48 @@ +const totalTill = require("./till"); + +test("Given a non object, it should throw error message", () => { + const errorMessage = "Input should be an object"; + expect(() => totalTill([])).toThrow(errorMessage); + expect(() => totalTill(123)).toThrow(errorMessage); + expect(() => totalTill("123")).toThrow(errorMessage); + expect(() => totalTill(null)).toThrow(errorMessage); + expect(() => totalTill(undefined)).toThrow(errorMessage); + expect(() => totalTill(true)).toThrow(errorMessage); +}); + +test("Given an empty object, it should return £0.00", () => { + expect(totalTill({})).toBe("£0.00"); +}); + +test("Given a pence object, it should return value of pound", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + }; + expect(totalTill(till)).toBe("£4.40"); +}); + +test("Given a large amount of pence object, it should return proper value of pound", () => { + const till = { + "1p": 1234, + "5p": 567, + "50p": 890, + "20p": 1011, + }; + expect(totalTill(till)).toBe("£687.89"); +}); + +test("Given a pence object mixed with something else, it should ignore invalid input and return the value of pound", () => { + const till = { + "1p": 10, + "5p": 6, + fakeCoin: 10, + undefined: 1, + null: 2, + "50p": 4, + "20p": 10, + }; + expect(totalTill(till)).toBe("£4.40"); +}); From d5a16f532fe671f9c699a6d2f4da83e64821ea58 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Wed, 22 Oct 2025 11:39:45 +0100 Subject: [PATCH 13/14] simplify ingredients printing --- Sprint-2/debug/recipe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 1e10c475b..bc4a57e40 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -16,7 +16,7 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe.ingredients.map((ingredient) => `${ingredient}`).join("\n")}`); +${recipe.ingredients.join("\n")}`); // After the first attempt, I realized that I can't use a for loop inside ${} // so, I tried map function to get each ingredient and added \n between them. From 5f76350ed19a691dfff366c2c65ec2eba164b847 Mon Sep 17 00:00:00 2001 From: TzeMingHo Date: Wed, 22 Oct 2025 12:47:23 +0100 Subject: [PATCH 14/14] updated to frequency map not an array --- Sprint-2/stretch/mode.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 1ad6ed0a1..4e9cccdf6 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -21,10 +21,10 @@ function trackFrequency(list) { return frequency; } -function highestFrequency(frequencyArray) { +function highestFrequency(frequencyMap) { let maxFreq = 0; let mode; - for (let [num, freq] of frequencyArray) { + for (let [num, freq] of frequencyMap) { if (freq > maxFreq) { mode = num; maxFreq = freq; @@ -35,11 +35,13 @@ function highestFrequency(frequencyArray) { function calculateMode(list) { // track frequency of each value - const frequencyArray = trackFrequency(list); + const frequencyMap = trackFrequency(list); // Find the value with the highest frequency - return highestFrequency(frequencyArray); + return highestFrequency(frequencyMap); } +calculateMode([1, 3, "2", 2, 3, null]); + module.exports = calculateMode;