diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..b0b0fa890 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,4 +1,6 @@ // Predict and explain first... +//Its will say undefined. This is because the address object does not have an index of 0. +// To access the houseNumber property of the address object, we need to use dot notation or bracket notation with the property name as a string // 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..150c86742 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,7 +1,8 @@ // Predict and explain first... - +// It will throw a TypeError.Because a for of loop only can go throuhgh things like strings and arrays but not object. // 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 +// To print the value of the object , we use object.values() because it turn the object into list of value that er can loop through and log. const author = { firstName: "Zadie", @@ -11,6 +12,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.values(author)) { console.log(value); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..60c52a355 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,9 +1,10 @@ // Predict and explain first... - +// It will trown an error. However my perdiction was wrong it doesn't throw errow ,instead it print the ingredients array and [ object object] +// because of the whole recipe object is being converted to string. // 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? - +// I fixed by used the join () method to print the each ingredient on new line. const recipe = { title: "bruschetta", serves: 2, @@ -12,4 +13,4 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join('\n ')}`); diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..c18b20de8 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,9 @@ -function contains() {} +function contains(obj, prop) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + + return Object.prototype.hasOwnProperty.call(obj, prop); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..c12d86abb 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,26 @@ 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({}, "a")).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("returns true when property exists", () => { + 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("return false when property does not exit", () => { + expect(contains({ a: 1, b: 2 }, "c")).to(fasle); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("return false for invalid input like an array", () => { + expect(contains([], "a")).toBe(false); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..0982efe9c 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,14 @@ -function createLookup() { - // implementation here +function createLookup(pairs) { + if (!Array.isArray(pairs)) return {}; + const lookup = {}; + + for (const pair of pairs) { + if (!Array.isArray(pair) || pair.length !== 2) continue; + + const [country, currency] = pair; + lookup[country] = currency; + } + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..11ab7dfa2 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,33 @@ 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 result = createLookup(input); + + expect(result).toEqual({ + US: "USD", + CA: "CAD", + }); +}); +test("returns an empty object for an empty array", () => { + expect(createLookup([])).toEqual({}); +}); + +test("returns an empty object for invalid input", () => { + expect(createLookup("not-an-array")).toEqual({}); +}); + +test("ignores invalid pairs inside array", () => { + const input = [["US", "USD"], ["CA"], "invalid"]; + + expect(createLookup(input)).toEqual({ + US: "USD", + }); +}); /* @@ -15,6 +42,7 @@ Given When - createLookup function is called with the country-currency array as an argument + Then - It should return an object where: - The keys are the country codes diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..65ab80416 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,12 +1,20 @@ function parseQueryString(queryString) { - const queryParams = {}; - if (queryString.length === 0) { - return queryParams; + if (typeof queryString !== "string" || queryString.length === 0) { + return {}; } + + const queryParams = {}; const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + if (!pair) continue; + + const index = pair.indexOf("="); + if (index === -1) continue; + + const key = pair.slice(0, index); + const value = pair.slice(index + 1); + queryParams[key] = value; } diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..daf391ddc 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,32 @@ // 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("returns empty object for empty string", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("returns empty object for empty string", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("parses multiple key value pairs", () => { + expect(parseQueryString("a=1&b=2")).toEqual({ + a: "1", + b: "2", + }); +}); + +test("ignores invalid pairs without =", () => { + expect(parseQueryString("a=1&invalid&b=2")).toEqual({ + a: "1", + b: "2", }); }); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..0bdcdb4e5 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("Expected 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..7c06c7978 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,69 @@ 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 with duplicate items returns correct counts", () => { + expect(tally(['a', 'a', 'b', 'c'])).toEqual({ a: 2, b: 1, c: 1 }); +}); // 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 +const tally = require("./tally.js"); + +/** + * tally array + * + * In this task, you'll need to implement a function called tally + * that will take a list of items and count the frequency of each item + * in an array + * + * For example: + * + * tally(['a']), target output: { a: 1 } + * tally(['a', 'a', 'a']), target output: { a: 3 } + * tally(['a', 'a', 'b', 'c']), target output: { a : 2, b: 1, c: 1 } + */ + +// Acceptance criteria: + +// 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 with duplicate items returns correct counts", () => { + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 }); +}); + +// Given an empty array +// When passed to tally +// Then it should return 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 countss for each unique item", () => { + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 }); +}); + +// Given an invalid input like a string +// When passed to tally +// Then it should throw an error +test(" tally throws an error for invalid input", () => { + expect(() => tally("not-an-array")).toThrow(); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test(" tally throws an error for invalid input", () => { + expect(() => tally("not-an-array")).toThrow(); +}); \ No newline at end of file diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..47ded0753 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -17,13 +17,42 @@ function invert(obj) { } // 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} +return { 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 an object. +//It is needed so we can loop through the object and access both keys and values. +Object.entries({ a: 1, b: 2 }); +// [["a", 1], ["b", 2]] // d) Explain why the current return value is different from the target output - +//The current output is wrong because invertedObj.key uses a fixed property name instead of the variable key, so it overwrites the same property each time. +//It should use invertedObj[value] = key to properly swap keys and values. // e) Fix the implementation of invert (and write tests to prove it's fixed!) +const invert = require("./invert.js"); + +test("inverts a normal object", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ + 1: "a", + 2: "b", + }); +}); + +test("inverts a single pair", () => { + expect(invert({ x: 10 })).toEqual({ + 10: "x", + }); +}); + +test("returns empty object when input is empty", () => { + expect(invert({})).toEqual({}); +}); diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..e1cfbbfc4 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -1,28 +1,24 @@ -/* - Count the number of times a word appears in a given string. - - Write a function called countWords that - - takes a string as an argument - - returns an object where - - the keys are the words from the string and - - the values are the number of times the word appears in the string - - Example - If we call countWords like this: - - countWords("you and me and you") then the target output is { you: 2, and: 2, me: 1 } - - To complete this exercise you should understand - - Strings and string manipulation - - Loops - - Comparison inside if statements - - Setting values on an object - -## Advanced challenges - -1. Remove all of the punctuation (e.g. ".", ",", "!", "?") to tidy up the results - -2. Ignore the case of the words to find more unique words. e.g. (A === a, Hello === hello) - -3. Order the results to find out which word is the most common in the input -*/ +function countWords(str) { + // handle empty or invalid input + if (typeof str !== "string") { + throw new Error("Expected a string"); + } + + // 1. clean string (remove punctuation + lowercase) + const cleanStr = str.replace(/[.,!?]/g, "").toLowerCase(); + + // 2. split into words + const words = cleanStr.split(" "); + + // 3. count words + const result = {}; + for (const word of words) { + if (word !== "") { + result[word] = (result[word] || 0) + 1; + } + } + + return result; +} + +module.exports = countWords; diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..e28c49984 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,29 +8,40 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { - // track frequency of each value +function getFrequencies(list) { let freqs = new Map(); for (let num of list) { - if (typeof num !== "number") { - continue; - } + if (typeof num !== "number") continue; freqs.set(num, (freqs.get(num) || 0) + 1); } - // Find the value with the highest frequency + return freqs; +} + +function findMode(freqs) { let maxFreq = 0; let mode; + for (let [num, freq] of freqs) { if (freq > maxFreq) { - mode = num; maxFreq = freq; + mode = num; } } return maxFreq === 0 ? NaN : mode; } +function calculateMode(list) { + const freqs = getFrequencies(list); + return findMode(freqs); +} + module.exports = calculateMode; + +// Explanation: +getFrequencies(list) //counts how many times each number appears +findMode(freqs) // finds the number with the highest frequency +calculateMode(list) // combines both steps \ No newline at end of file diff --git a/Sprint-2/stretch/mode.test.js b/Sprint-2/stretch/mode.test.js index ca33c28a3..a9576c450 100644 --- a/Sprint-2/stretch/mode.test.js +++ b/Sprint-2/stretch/mode.test.js @@ -5,28 +5,43 @@ const calculateMode = require("./mode.js"); // Given an array of numbers // When calculateMode is called on the array // Then it should return the number that appears most frequently in the array - -// Example: -// Given [2,4,1,2,3,2,1] -// When calculateMode is called on [2,4,1,2,3,2,1] -// Then it should return 2 */ - +// Answer: describe("calculateMode()", () => { test("returns the most frequent number in an array", () => { const nums = [2, 4, 1, 2, 3, 2, 1]; - expect(calculateMode(nums)).toEqual(2); }); - test("returns the first mode in case of multiple modes", () => { - const nums = [1, 2, 2, 3, 3]; + // Example: + // Given [2,4,1,2,3,2,1] + // When calculateMode is called on [2,4,1,2,3,2,1] + // Then it should return 2 + // Answer: + test("returns 2 as the most frequent number", () => { + expect(calculateMode([2, 4, 1, 2, 3, 2, 1])).toEqual(2); + }); - expect(calculateMode(nums)).toEqual(2); + // Given an array with multiple modes + // When calculateMode is called + // Then it should return the first mode + // Answer: + test("returns first mode when there is a tie", () => { + expect(calculateMode([1, 2, 2, 3, 3])).toEqual(2); }); + // Given an array with non-number values + // When calculateMode is called + // Then it should ignore non-number values + // Answer: test("ignores non-number values", () => { - const nums = [1, 3, "2", 2, 3, null]; + expect(calculateMode([1, 3, "2", 2, 3, null])).toEqual(3); + }); - expect(calculateMode(nums)).toEqual(3); + // Given an empty array + // When calculateMode is called + // Then it should return NaN + // Answer: + test("returns NaN for empty array", () => { + expect(calculateMode([])).toBeNaN(); }); }); diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..9ec49c7c1 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -23,9 +23,38 @@ const till = { const totalAmount = totalTill(till); // a) What is the target output when totalTill is called with the till object +("£4.40"); // b) Why do we need to use Object.entries inside the for...of loop in this function? +// We use Object.entries() to convert the object into key-value pairs so we can loop through each coin type and its quantity using a for...of loop. // c) What does coin * quantity evaluate to inside the for...of loop? +//It calculates the total value in pence for each coin type. + +//"1p" * 10 = 10 +//"50p" * 4 = 200 + +// So it gives the total pence for each coin type. // d) Write a test for this function to check it works and then fix the implementation of totalTill +Eg: test("calculates total till value correctly", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + }; + + expect(totalTill(till)).toEqual("£4.40"); +}); +//then fix the implementation of totalTill +function totalTill(till) { + let total = 0; + + for (const [coin, quantity] of Object.entries(till)) { + const value = parseInt(coin); + total += value * quantity; + } + + return `£${(total / 100).toFixed(2)}`; +}