From e4288da17430ac7dcd4940ca0f216bbb1198c8a5 Mon Sep 17 00:00:00 2001 From: Mohsen Zamani Date: Tue, 3 Feb 2026 10:02:46 +0000 Subject: [PATCH] complete feature/sprint-2 --- Sprint-2/debug/address.js | 6 ++++- Sprint-2/debug/author.js | 8 +++++-- Sprint-2/debug/recipe.js | 10 +++++--- Sprint-2/implement/contains.js | 9 ++++++- Sprint-2/implement/contains.test.js | 18 +++++++++++++- Sprint-2/implement/lookup.js | 8 +++++-- Sprint-2/implement/lookup.test.js | 23 ++++++++++++++++-- Sprint-2/implement/querystring.js | 25 +++++++++++++++++-- Sprint-2/implement/querystring.test.js | 33 ++++++++++++++++++++++++-- Sprint-2/implement/tally.js | 8 ++++++- Sprint-2/implement/tally.test.js | 12 +++++++++- Sprint-2/interpret/invert.js | 10 +++++++- Sprint-2/interpret/invert.test.js | 17 +++++++++++++ Sprint-2/stretch/count-words.js | 11 +++++++++ Sprint-2/stretch/mode.js | 16 +++++++++---- Sprint-2/stretch/till.js | 16 +++++++++++-- 16 files changed, 205 insertions(+), 25 deletions(-) create mode 100644 Sprint-2/interpret/invert.test.js diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..7dfb96256 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -12,4 +12,8 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address["houseNumber"]}`); + +// Code returns undefined +// because address is an object but it is accessed like an array +// in an object values can be accessed by using their proper keys diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..06ad789b6 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -11,6 +11,10 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); +for (const key in author) { + console.log(author[key]); } + +// for iterating in an object for...in can be used +// and each iteration it returns the key +// then each value can be accessed diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..042d0c9cc 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -10,6 +10,10 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +console.log(`${recipe.title} serves ${recipe.serves}\ningredients:`); +recipe.ingredients.forEach((ingredient) => console.log(ingredient)); + +// everything is logged on one line +// console.log is logging the recipe object array as a whole +// instead of each value of ingredients array +// the ingredients array must be logged using a loop diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..9e8d370be 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,10 @@ -function contains() {} +function contains(obj, property) { + try { + const keys = Object.keys(obj); + return keys.includes(property); + } catch (error) { + throw new Error("The parameter given is not a plain JS object."); + } +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..9543962e4 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,32 @@ 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({}, "key1")).toEqual(false)); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("contains returns true when object contains the given property", () => + expect(contains({ key1: "value1", key2: "value2" }, "key1")).toEqual(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 when object does not contain the given property", () => + expect(contains({ key1: "value1", key2: "value2" }, "key4")).toEqual(false)); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +it("contains returns false or throws an error if given parameter is not a valid object", () => { + expect(contains([], "key1")).toEqual(false); + expect(contains("key1:value1", "key1")).toEqual(false); + expect(contains(5235, "key1")).toEqual(false); + expect(() => contains(undefined, "key1")).toThrow( + "The parameter given is not a plain JS object." + ); + expect(() => contains(null, "key1")).toThrow( + "The parameter given is not a plain JS object." + ); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..1a6e9e88a 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,9 @@ -function createLookup() { - // implementation here +function createLookup(countryCurrencyPairs) { + if (!Array.isArray(countryCurrencyPairs)) return {}; + return countryCurrencyPairs.reduce((acc, curr) => { + acc[curr[0]] = curr[1]; + return acc; + }, {}); } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..ddf69b5d0 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,7 +1,5 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); - /* Create a lookup object of key value pairs from an array of code pairs @@ -33,3 +31,24 @@ It should return: 'CA': 'CAD' } */ + +describe("createLookup", () => { + it("returns empty object if the parameter passed is not an array", () => { + expect(createLookup("US:USD")).toEqual({}); + expect(createLookup(undefined)).toEqual({}); + }); + + it("returns an object of country initials and currency code", () => + expect( + createLookup([ + ["US", "USD"], + ["CA", "CAD"], + ]) + ).toEqual({ + US: "USD", + CA: "CAD", + })); + + it("returns an empty object if passed an empty array", () => + expect(createLookup([])).toEqual({})); +}); diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..2adb32ccc 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -6,8 +6,29 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + let key = "", + value = ""; + let equalSignIndex = pair.indexOf("="); + + if (equalSignIndex === -1) { + key = pair; + value = ""; + } else { + key = pair.slice(0, equalSignIndex); + value = pair.slice(equalSignIndex + 1); + } + + const existingKeys = Object.keys(queryParams); + if (key === "" && value === "") continue; + if (existingKeys.includes(key)) { + if (queryParams[key] === value) continue; + if (Array.isArray(queryParams[key])) { + queryParams[key].push(value); + } else { + const temp = queryParams[key]; + queryParams[key] = [temp, value]; + } + } else queryParams[key] = value; } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..3c0f29c76 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,39 @@ // 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("parses querystring values containing repetitive keys", () => { + expect(parseQueryString("a=1&b=6&a=2&a=3&b=7")).toEqual({ + a: ["1", "2", "3"], + b: ["6", "7"], + }); +}); + +test("parses querystring values containing repetitive keys with same values", () => { + expect(parseQueryString("a=1&b=6&a=2&a=3&b=6")).toEqual({ + a: ["1", "2", "3"], + b: "6", + }); +}); + +test("parses querystring values containing no key and value but only =", () => { + expect(parseQueryString("=&b=6&a=2&=&=")).toEqual({ + a: "2", + b: "6", + }); +}); + +test("parses querystring values missing = sign", () => { + expect(parseQueryString("id=5&name=mohsen&age")).toEqual({ + id: "5", + name: "mohsen", + age: "", }); }); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..aebba20a1 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,9 @@ -function tally() {} +function tally(list) { + if (!Array.isArray(list)) throw new Error("Not an array."); + return list.reduce((acc, curr) => { + acc[curr] = (acc[curr] || 0) + 1; + return acc; + }, {}); +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..39a26f38e 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,22 @@ 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 an object with the count of items", () => { + expect(tally(["a"])).toEqual({ a: 1 }); + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 }); + expect(tally(["a", "a", "a"])).toEqual({ a: 3 }); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("tally throws an error if passed not an array", () => { + expect(() => tally("not an array")).toThrow("Not an array."); + expect(() => tally({})).toThrow("Not an array."); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..d2bf9f28d 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,20 +10,28 @@ function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; } return invertedObj; } +module.exports = invert; + // 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? +// it returns each pair of key and value as array. +// it is needed for iteration through an object // d) Explain why the current return value is different from the target output +// because the place of key and value are not swapped // 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..0a8bc004e --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,17 @@ +const invert = require("./invert.js"); + +describe("invert", () => { + it("returns empty object if passed empty object", () => + expect(invert({})).toEqual({})); + + it("returns inverted object of the passed object", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ 1: "a", 2: "b" }); + expect(invert({ a: 1, b: 2, c: 3, d: 4, e: 5 })).toEqual({ + 1: "a", + 2: "b", + 3: "c", + 4: "d", + 5: "e", + }); + }); +}); diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..1095b0886 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,14 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(string) { + const noPunctuationStr = string.replace(/[.,!?]/g, ""); + const wordArray = noPunctuationStr.split(" "); + let wordCount = new Map(); + for (let word of wordArray) { + wordCount.set(word, (wordCount.get(word) || 0) + 1); + } + const sortedWordCount = [...wordCount.entries()].sort((a, b) => b[1] - a[1]); + return sortedWordCount; +} diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..c376e4112 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,8 +8,8 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { - // track frequency of each value +// track frequency of each value +function calculateFreq(list) { let freqs = new Map(); for (let num of list) { @@ -19,18 +19,26 @@ function calculateMode(list) { freqs.set(num, (freqs.get(num) || 0) + 1); } + return freqs; +} - // Find the value with the highest frequency +// Find the value with the highest frequency +function findMaxFreq(freqs) { let maxFreq = 0; let mode; + for (let [num, freq] of freqs) { if (freq > maxFreq) { mode = num; maxFreq = freq; } } + return [maxFreq, mode]; +} +function calculateMode(list) { + const calculatedFrequency = calculateFreq(list); + const [maxFreq, mode] = findMaxFreq(calculatedFrequency); return maxFreq === 0 ? NaN : mode; } - module.exports = calculateMode; diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..42637059c 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -8,10 +8,11 @@ function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + const valueOfCoin = Number(coin.slice(0, -1)); + total += valueOfCoin * quantity; } - return `£${total / 100}`; + return `£${(total / 100).toFixed(2)}`; } const till = { @@ -22,10 +23,21 @@ const till = { }; const totalAmount = totalTill(till); +const assertEquals = (actual, expected) => { + console.assert( + actual === expected, + `Received ${actual} but expected ${expected}.` + ); +}; + // a) What is the target output when totalTill is called with the till object +// £4.4 // b) Why do we need to use Object.entries inside the for...of loop in this function? +// to iterate in an object and access the [key, value] pair // c) What does coin * quantity evaluate to inside the for...of loop? +// it multiplies keys and values of till // d) Write a test for this function to check it works and then fix the implementation of totalTill +assertEquals(totalAmount, "£4.40");