diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..9d3ae06c2 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,5 @@ // Predict and explain first... - +// this code will give an error because the address is not an array and we should use dot notation to access houseNumber. // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -12,4 +12,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..339460094 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,5 @@ // Predict and explain first... - +// this code will give an error because the for...of is generally used to iterate over iterable objects like arrays or strings. thus, we should use for...in loop to iterate over the properties of an 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 @@ -11,6 +11,6 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); -} +for (const value in author) { + console.log(`${value}: ${author[value]}`); +}; diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..6819c8244 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,5 @@ // Predict and explain first... - +// This code will not log out the ingredients correctly.we should access the ingredients array specifically. // 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? @@ -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} +ingredients:`); + +recipe.ingredients.forEach(ingredient => { + console.log(ingredient); +}); \ No newline at end of file diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..f147caeaa 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(input, propertyName) { + if (input && typeof input === 'object' && !Array.isArray(input)) { + return input.hasOwnProperty(propertyName); + } + return false; +}; module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..90478d58f 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,52 @@ 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("given a contains function ,returns true for existing property, false otherwise", () => { + const input = {a: 1, b: 2}; + const propertyName = 'a'; + const currentOutput = contains(input, propertyName); + const targetOutput = true; + expect(currentOutput).toBe(targetOutput); +}); // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); - +test("given an empty object, contains returns false",()=>{ + const input = {}; + const propertyName = 'anyProp'; + const currentOutput = contains(input,propertyName); + const targetOutput = false; + expect(currentOutput).toBe(targetOutput); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("given an object with properties, returns true for existing property",()=>{ + const input = {x:10,y:20,z:30}; + const propertyName = 'y'; + const currentOutput = contains(input,propertyName); + const targetOutput = true; + expect(currentOutput).toBe(targetOutput); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("given an object with properties, returns false for non-existing property",()=>{ + const input = {x:10,y:20,z:30}; + const propertyName = 'a'; + const currentOutput = contains(input,propertyName); + const targetOutput = false; + expect(currentOutput).toBe(targetOutput); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("given invalid parameters like an array, contains returns false",()=>{ + const input = [1,2,3]; + const propertyName = '0'; + const currentOutput = contains(input,propertyName); + const targetOutput = false; + expect(currentOutput).toBe(targetOutput); +}); \ No newline at end of file diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..d21e67073 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 [countryCode, currencyCode] of pairs) { + lookup[countryCode] = currencyCode; + } + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..5e5301bda 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,15 @@ 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'], ['GB', 'GBP']]; + const currentOutput = createLookup(input); + const targetOutput = { + 'US': 'USD', + 'CA': 'CAD', + 'GB': 'GBP' + }; + expect(currentOutput).toEqual(targetOutput); +}); /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..33eed4d43 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,12 +1,33 @@ function parseQueryString(queryString) { const queryParams = {}; - if (queryString.length === 0) { + + if (!queryString) { return queryParams; } + + if (queryString.startsWith("?")) { + queryString = queryString.slice(1); + } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + if (!pair) continue; + + const firstEq = pair.indexOf("="); + + let key; + let value; + + if (firstEq === -1) { + // No "=" found → key with empty value + key = decodeURIComponent(pair); + value = ""; + } else { + key = decodeURIComponent(pair.slice(0, firstEq)); + value = decodeURIComponent(pair.slice(firstEq + 1)); + } + queryParams[key] = value; } @@ -14,3 +35,4 @@ function parseQueryString(queryString) { } module.exports = parseQueryString; +console.log(parseQueryString("?text=Hello%20World&amount=5%25")); diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..8fce8228b 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -6,7 +6,21 @@ const parseQueryString = require("./querystring.js") test("parses querystring values containing =", () => { - expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", - }); + expect(parseQueryString("equation=x=y+1")).toEqual({ equation: "x=y+1" }); }); + +test("parses multiple key=value pairs", () => { + expect(parseQueryString("a=1&b=2")).toEqual({ a: "1", b: "2" }); +}); + +test("parses empty querystring", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("parses key with no '=' (a)", () => { + expect(parseQueryString("a")).toEqual({ a: "" }); +}); + +test("last value wins for duplicate keys", () => { + expect(parseQueryString("a=1&a=2")).toEqual({ a: "2" }); +}); \ No newline at end of file diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..8b09ead0d 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,22 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new TypeError('Input must be an array'); + } + + const tallyResult = Object.create(null); + + for (const item of items) { + if (tallyResult[item] === undefined) { + tallyResult[item] = 1; + } else { + tallyResult[item]++; + } + } + + return tallyResult; +} module.exports = tally; + +console.log("Test : tally with 'toString':"); +console.log(tally(["toString", "toString"])); diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..64391033d 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -20,15 +20,35 @@ const tally = require("./tally.js"); // When passed an array of items // Then it should return an object containing the count for each unique item +test("tally returns counts for each unique item", () => { + const input = ['a', 'a', 'b', 'c']; + const expectedOutput = { a: 2, b: 1, c: 1 }; + expect(tally(input)).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 = {}; + expect(tally(input)).toEqual(expectedOutput); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("tally counts each unique item correctly", () => { + const input = ['a', 'b', 'a', 'c', 'b', 'a']; + const expectedOutput = { a: 3, b: 2, c: 1 }; + expect(tally(input)).toEqual(expectedOutput); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error + +test("tally throws an error for non-array input", () => { + const input = "not an array"; + expect(() => tally(input)).toThrow(TypeError); +}); \ No newline at end of file diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..7db085cdf 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,24 +6,69 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} +//function invert(obj) { +// const invertedObj = {}; + + // for (const [key, value] of Object.entries(obj)) { + // invertedObj.key = value; + // } + + // 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}: {"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 iterate over each key-value pair in the object so that we can swap them when creating the inverted 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 in the line (invertedObj.key = value;), the property name 'key' is being used literally instead of using the variable (key). This means that every iteration of the loop is overwriting the same property 'key' in the inverted object, resulting in only the last value being stored. + +// e) Fix the implementation of invert (and write tests to prove it's fixed!) + +// Fixed implementation: + + 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 } -// b) What is the current return value when invert is called with { a: 1, b: 2 } +// Test cases: -// c) What is the target return value when invert is called with {a : 1, b: 2} +const invert = require('./invert'); -// c) What does Object.entries return? Why is it needed in this program? +// single key: -// d) Explain why the current return value is different from the target output +test('invert single key-value pair', () => { + expect(invert({ a: 1 })).toEqual({ "1": "a" }); +}); -// e) Fix the implementation of invert (and write tests to prove it's fixed!) +// multiple keys: + +test('invert multiple key-value pairs', () => { + expect(invert({ a: 1, b: 2 })).toEqual({ "1": "a", "2": "b" }); +}); + +// Empty object: + +test('invert empty object', () => { + expect(invert({})).toEqual({}); +}); + +// Non-string values: + +test('invert with non-string values', () => { + expect(invert({ x: 10, y: 20 })).toEqual({ "10": "x", "20": "y" }); +}); \ No newline at end of file diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..958261e70 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,22 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(inputString) { + + const cleanedString = inputString.replace(/[.,!?]/g, '').toLowerCase(); + const wordsArray = cleanedString.split(/\s+/); + const wordCount = {}; + + for (const word of wordsArray) { + if (wordCount[word] === undefined) { + wordCount[word] = 1; + } else { + wordCount[word]++; + } + } + + return wordCount; +} + +module.exports = countWords; \ No newline at end of file diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..055e5b063 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,29 +8,42 @@ // 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(); - for (let num of list) { - if (typeof num !== "number") { - continue; - } +// Helper function to count frequency of numbers: +function countFrequencies(list) { + const freqs = new Map(); + + for (const 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; +} + +// Helper function to find mode from frequency map: + +function findMode(freqs) { let maxFreq = 0; let mode; - for (let [num, freq] of freqs) { + + for (const [num, freq] of freqs) { if (freq > maxFreq) { - mode = num; maxFreq = freq; + mode = num; } } return maxFreq === 0 ? NaN : mode; } + +// Main function: + +function calculateMode(list) { + const freqs = countFrequencies(list); + return findMode(freqs); +} + module.exports = calculateMode; diff --git a/Sprint-2/stretch/mode.test.js b/Sprint-2/stretch/mode.test.js index ca33c28a3..742ada992 100644 --- a/Sprint-2/stretch/mode.test.js +++ b/Sprint-2/stretch/mode.test.js @@ -5,28 +5,37 @@ 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 - +describe("calculateMode()", () => { + test("is a function and returns the most frequent number", () => { + const nums = [1, 2, 2, 3, 4]; + expect(typeof calculateMode).toBe("function"); + expect(calculateMode(nums)).toBe(2); + }); // 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 */ -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]; - expect(calculateMode(nums)).toEqual(2); }); test("ignores non-number values", () => { const nums = [1, 3, "2", 2, 3, null]; - expect(calculateMode(nums)).toEqual(3); }); + + test("returns NaN for an empty array", () => { + expect(calculateMode([])).toBeNaN(); + }); + + test("returns NaN if array has no numbers", () => { + expect(calculateMode(["a", "b", null])).toBeNaN(); + }); }); diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..a8ca5eb72 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -4,16 +4,47 @@ // When this till object is passed to totalTill // Then it should return the total amount in pounds +//function totalTill(till) { +//let total = 0; + +//for (const [coin, quantity] of Object.entries(till)) { +// total += coin * quantity; +//} + +// return `£${total / 100}`; +//} + +//const till = { +// "1p": 10, +// "5p": 6, +// "50p": 4, +// "20p": 10, +//}; +//const totalAmount = totalTill(till); + +// a) What is the target output when totalTill is called with the till object? +// Answer: £4.10 + +// b) Why do we need to use Object.entries inside the for...of loop in this function? +// Answer: Object.entries is used to convert the till object into an array of [key, value] pairs, allowing us to iterate over each coin type and its quantity. +// c) What does coin * quantity evaluate to inside the for...of loop? +// Answer: coin * quantity evaluates to NaN because coin is a string (e.g., "1p") and cannot be directly multiplied by a number. + +// d) Write a test for this function to check it works and then fix the implementation of totalTill? + + +// Fix implementation function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + // Remove the 'p' and convert to number + const coinValue = Number(coin.replace("p", "")); + total += coinValue * quantity; } return `£${total / 100}`; } - const till = { "1p": 10, "5p": 6, @@ -22,10 +53,34 @@ const till = { }; const totalAmount = totalTill(till); -// a) What is the target output when totalTill is called with the till object +module.exports = totalTill; -// b) Why do we need to use Object.entries inside the for...of loop in this function? +// test cases: -// c) What does coin * quantity evaluate to inside the for...of loop? +const totalTill = require("./till.js"); + +test("totalTill calculates the total amount in pounds from the till object", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + }; + + expect(totalTill(till)).toBe("£4.10"); +}); + +// Additional test cases +test("totalTill returns £0.00 for an empty till", () => { + const till = {}; + + expect(totalTill(till)).toBe("£0.00"); +}); + +test("totalTill handles tills with only one type of coin", () => { + const till = { + "100p": 5, + }; -// d) Write a test for this function to check it works and then fix the implementation of totalTill + expect(totalTill(till)).toBe("£5.00"); +});