From b4cca094db9859658af1be1fff5c3e1754664d2a Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 11:18:55 +0000 Subject: [PATCH 1/8] Debug exercises completed --- Sprint-2/debug/address.js | 4 +++- Sprint-2/debug/author.js | 4 +++- Sprint-2/debug/recipe.js | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..36d82085a 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,4 +1,6 @@ // Predict and explain first... +// As this is an object (key-value pairs), the elements cannot be accessed through indexing like array. +// Values can be accessed through their relevant key // This code should log out the houseNumber from the address object // but it isn't working... @@ -12,4 +14,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..622620136 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,4 +1,6 @@ // Predict and explain first... +// As object is key-value pairs of data, it isn't exactly iterable like arrays. +// We can loop over the keys or values of an object using Object.entries() // 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,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const [key, value] of Object.entries(author)) { console.log(value); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..bd012af67 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,4 +1,5 @@ // Predict and explain first... +// It is not accessing the ingredients array correctly. // This program should log out the title, how many it serves and the ingredients. // Each ingredient should be logged on a new line @@ -12,4 +13,17 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); + + +// approach 2 + +// let msg = ''; + +// for (const ingredient of recipe.ingredients) { +// msg += ingredient + '\n'; +// } + +// console.log(`${recipe.title} serves ${recipe.serves} +// ingredients: +// ${msg}`); \ No newline at end of file From 5187f6416b4d35367fc474ca793c954e3a52c112 Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 14:08:12 +0000 Subject: [PATCH 2/8] Implement exercises done --- Sprint-2/implement/contains.js | 12 ++++++++++- Sprint-2/implement/contains.test.js | 24 +++++++++++++++++++++- Sprint-2/implement/lookup.js | 6 +++--- Sprint-2/implement/lookup.test.js | 16 ++++++++++++++- Sprint-2/implement/querystring.js | 3 ++- Sprint-2/implement/querystring.test.js | 23 +++++++++++++++++++++ Sprint-2/implement/tally.js | 25 ++++++++++++++++++++++- Sprint-2/implement/tally.test.js | 28 +++++++++++++++++++++++++- 8 files changed, 128 insertions(+), 9 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..977db9e6c 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,13 @@ -function contains() {} +function contains(obj, x) { + for (const key in obj) { + if (Object.hasOwn(obj, key) && key == x) { + return true + } + } + return false +} module.exports = contains; + +// Object.hasOwn(obj, key) +// This ensures the property belongs directly to the object, not its prototype. \ No newline at end of file diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..8d1bf7179 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,38 @@ 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", () => { + expect(contains({})).toStrictEqual(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 an existing property name returns true", () => { + expect(contains({a: 1, b: 2, c: 3}, 'c')).toStrictEqual(true); + expect(contains({name: "John Doe", job: "developer"}, 'job')).toStrictEqual(true) + expect(contains({color: "blue", size: "xxl", madeOf: "China"}, 'madeOf')).toStrictEqual(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 properties with non-existent property name return false", () => { + expect(contains({a: 1, b: 2, c: 3}, 'g')).toStrictEqual(false); + expect(contains({a: 1, b: 2, c: 3}, 'abc')).toStrictEqual(false); + expect(contains({name: "John Doe", age: 31}, 'job')).toStrictEqual(false); +}) + // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error + +test("contains on invalid parameters returns false", () => { + expect(contains(["a", "b", "c"])).toStrictEqual(false); + expect(contains("I am a string")).toStrictEqual(false); + expect(contains(777)).toStrictEqual(false); +}) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..c1911e518 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,5 @@ -function createLookup() { - // implementation here +function createLookup(arr) { + return Object.fromEntries(arr); } -module.exports = createLookup; +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..88517abf9 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,20 @@ 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 givenInput = createLookup(input); + const targetOutput = {US: 'USD', CA: 'CAD'}; + + expect(givenInput).toEqual(targetOutput); +}); + +test("creates a country currency code lookup for multiple codes", () => { + const input = [['AUS', 'AUD'], ['GB', 'GBP'], ['US', 'USD']]; + const givenInput = createLookup(input); + const targetOutput = {AUS: 'AUD', GB: 'GBP', US: 'USD'}; + + expect(givenInput).toEqual(targetOutput); +}) /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..223b30580 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -3,10 +3,11 @@ function parseQueryString(queryString) { if (queryString.length === 0) { return queryParams; } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + const [key, value] = pair.split(/=(.*)/s); queryParams[key] = value; } diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..aadfbc025 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -9,4 +9,27 @@ test("parses querystring values containing =", () => { expect(parseQueryString("equation=x=y+1")).toEqual({ "equation": "x=y+1", }); + expect(parseQueryString("token=abc=123=xyz")).toEqual({ + "token": "abc=123=xyz", + }); + expect(parseQueryString("shop=myshop.myshopify.com")).toEqual({ + "shop": "myshop.myshopify.com", + }); +}); + +test("parses querystring values containing = and &", () => { + expect(parseQueryString("category=shoes&color=black&size=10")).toEqual({ + category: "shoes", color: "black", size: "10" + }); + expect(parseQueryString("page=2&limit=10")).toEqual({ + page: "2", limit: "10" + }); +}); + +test("parses querystring values containing +", () => { + expect(parseQueryString("q=javascript+array+methods")).toEqual({ + q: "javascript+array+methods" + }) }); + + diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..c685bcc23 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,26 @@ -function tally() {} +function tally(arr) { + if (Array.isArray(arr)) { + + let countObj = {}; + + for (const item of arr) { + + if (countObj[item]) { + countObj[item] = countObj[item] + 1; + } else { + countObj[item] = 1; + } + } + + return countObj; + } + + else throw new Error(""); +} module.exports = tally; + +// console.log(tally(['a', 'a', 'a', 'a', 'a', 'b', 'c'])); + + +// tally(['a', 'a', 'a', 'a', 'a', 'b', 'c']), target output: { a : 5, b: 1, c: 1 } \ No newline at end of file diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..e9870bbaa 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,38 @@ 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("tally on an array with duplicate items returns the counts for each uniqe item", () => { + const input = ['a', 'a', 'b', 'c']; + const givenInput = tally(input); + const targetOutput = { a : 2, b: 1, c: 1 }; + + expect(givenInput).toEqual(targetOutput); +}) + +test("tally on an array with duplicate items returns the counts for each uniqe item", () => { + const input = ['a', 'a', 'bx', 'bx', 'b', 'b', 'b', 'b', 'c']; + const givenInput = tally(input); + const targetOutput = { a : 2, bx: 2, b: 4, c: 1 }; + + expect(givenInput).toEqual(targetOutput); +}) + // Given an invalid input like a string // When passed to tally // Then it should throw an error + +test("tall on invalid input throws an error", () => { + expect(() => tally("I am a string")).toThrow(); + expect(() => tally(777)).toThrow(); + expect(() => tally(true)).toThrow(); + expect(() => tally(undefined)).toThrow(); + expect(() => tally(null)).toThrow(); +}) From 67cf526a05c80ad2c99c92dba3b4ffa629547a2f Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 14:19:00 +0000 Subject: [PATCH 3/8] minor changes to querystring.js file --- Sprint-2/implement/querystring.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 223b30580..8eff0a700 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,13 +1,23 @@ function parseQueryString(queryString) { const queryParams = {}; - if (queryString.length === 0) { - return queryParams; - } + + if (!queryString) return queryParams; const keyValuePairs = queryString.split("&"); + // 1. with RegEx + // for (const pair of keyValuePairs) { + // const [key, value] = pair.split(/=(.*)/s); + // queryParams[key] = value; + // } + + // 2. with substring method for (const pair of keyValuePairs) { - const [key, value] = pair.split(/=(.*)/s); + const index = pair.indexOf("="); + + const key = pair.substring(0, index); + const value = pair.substring(index + 1); + queryParams[key] = value; } From 28a76d80ae0a010ca25e6f935999613814f5227b Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 14:30:38 +0000 Subject: [PATCH 4/8] invert function fixed --- Sprint-2/interpret/invert.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..aa2989bf5 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,20 +10,29 @@ function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; } return invertedObj; } // a) What is the current return value when invert is called with { a : 1 } + // { key: 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } + // { key: 2 } // c) What is the target return value when invert is called with {a : 1, b: 2} + // { key: 2 } // 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 here in order to loop over each of the key-value pairs // d) Explain why the current return value is different from the target output + // It is because `.key `is used on object. + // Instead [] should be used // e) Fix the implementation of invert (and write tests to prove it's fixed!) + // fixed the issue by using `invertedObj[value] = key` + // It is swaping the key and value in the returned object \ No newline at end of file From b2973a630129751947e3e92639ef6b2181f62322 Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 15:43:39 +0000 Subject: [PATCH 5/8] till.js exercise done --- Sprint-2/stretch/till.js | 7 ++++++- Sprint-2/stretch/till.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) 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..c0c0e50ce 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -8,7 +8,7 @@ function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + total += Number(coin.slice(0, -1)) * Number(quantity); } return `£${total / 100}`; @@ -22,10 +22,15 @@ const till = { }; const totalAmount = totalTill(till); +module.exports = totalTill; + // a) What is the target output when totalTill is called with the till object + // £NaN // b) Why do we need to use Object.entries inside the for...of loop in this function? + // to convert the given object into array // c) What does coin * quantity evaluate to inside the for...of loop? + // it converts different coins with different values into single pense // 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..ff692eee6 --- /dev/null +++ b/Sprint-2/stretch/till.test.js @@ -0,0 +1,29 @@ +const totalTill = require('./till'); + +test("given an object of coins, returns the total amount in pounds", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + }; + const givenInput = totalTill(till); + const expectedOutcome = "£4.4"; + + expect(givenInput).toEqual(expectedOutcome); +}); + +test("given an object of coins, returns the total amount in pounds", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + "2p": 2 + }; + const givenInput = totalTill(till); + const expectedOutcome = "£4.44"; + + expect(givenInput).toEqual(expectedOutcome); +}); + From 983f8921d1cee03b957853f94af302f552f1275b Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 15:44:02 +0000 Subject: [PATCH 6/8] count-words.js exercise done --- Sprint-2/stretch/count-words.js | 23 +++++++++++++++++++++++ Sprint-2/stretch/mode.test.js | 7 +++++++ 2 files changed, 30 insertions(+) diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..d5ff6209c 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,26 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(str) { + const arr = str.replace(/[^a-zA-Z0-9\s]/g, '').toLowerCase().split(" ") + + let countObj = {}; + + for (const item of arr) { + + if (countObj[item]) { + countObj[item] = countObj[item] + 1 + } else { + countObj[item] = 1; + } + } + + // `Object.entries()` converts object to array + const sortedArray = Object.entries(countObj).sort((a, b) => b[1] - a[1]); + + // `Object.fromEntries()` coverts the sorted array to object + return Object.fromEntries(sortedArray); +} + +console.log(countWords("you? and! me, and you. me me me me hello Me")); diff --git a/Sprint-2/stretch/mode.test.js b/Sprint-2/stretch/mode.test.js index ca33c28a3..a95ad7036 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("ignores non-number values", () => { + const nums = "CYF"; + + expect(calculateMode(nums)).toEqual(NaN); + }); + }); From 00e4862df145e9132c7d435a1bc897b40614e963 Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 15:47:50 +0000 Subject: [PATCH 7/8] minor changes in till.js --- Sprint-2/stretch/till.js | 2 +- Sprint-2/stretch/till.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index c0c0e50ce..af34480ef 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -11,7 +11,7 @@ function totalTill(till) { total += Number(coin.slice(0, -1)) * Number(quantity); } - return `£${total / 100}`; + return `£${(total / 100).toFixed(2)}`; } const till = { diff --git a/Sprint-2/stretch/till.test.js b/Sprint-2/stretch/till.test.js index ff692eee6..e99695680 100644 --- a/Sprint-2/stretch/till.test.js +++ b/Sprint-2/stretch/till.test.js @@ -8,7 +8,7 @@ test("given an object of coins, returns the total amount in pounds", () => { "20p": 10, }; const givenInput = totalTill(till); - const expectedOutcome = "£4.4"; + const expectedOutcome = "£4.40"; expect(givenInput).toEqual(expectedOutcome); }); From 6e5c0486b27ed34f8cebf7f63580572e1ad64ede Mon Sep 17 00:00:00 2001 From: Fida H Ali Zada Date: Mon, 9 Mar 2026 15:56:53 +0000 Subject: [PATCH 8/8] refactored calculateMode function --- Sprint-2/stretch/mode.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..90f0087d5 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,7 +8,7 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { +function calculateFrequency(list) { // track frequency of each value let freqs = new Map(); @@ -19,6 +19,11 @@ function calculateMode(list) { freqs.set(num, (freqs.get(num) || 0) + 1); } + return freqs; +} + +function calculateMode(list) { + const freqs = calculateFrequency(list); // Find the value with the highest frequency let maxFreq = 0;