diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..82d3102e8 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,4 +1,9 @@ // Predict and explain first... +// address is an object. +// JavaScript objects don’t have numeric indices like arrays do +// Or we have a key like "0". + + // This code should log out the houseNumber from the address object // but it isn't working... @@ -12,4 +17,6 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address["houseNumber"]}`); +console.log(`My house number is ${address.houseNumber}`); + diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..1e7399c5e 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,6 @@ // Predict and explain first... - +//author is a plain object, and JavaScript's for...of loop only works with iterable data types (like arrays, strings, etc.). Plain objects aren’t iterable +// we use Object.values(obj) to get property of 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 +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..518d5cb72 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,4 +1,6 @@ // Predict and explain first... +//recipe is an object, and when w insert an object directly into a string (like ${recipe}), +// javaScript converts it to a string using toString() // 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 +14,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..742d9f57d 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(object, key) { + if (typeof object !== "object" || object === null || Array.isArray(object)) { + throw new Error("Invalid input: expected an object"); + } + return object.hasOwnProperty(key); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..565c520b4 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,33 @@ 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("contains with an existing property name returns true", () => { + expect(contains ({a: 1, b: 2}, "a")).toBe(true); +}); // 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("contains with an existing property name", () => { + expect(contains({a: 1, b: 2}, "b")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains with a non-existent property name", () => { + expect(contains({a: 1, b: 2}, "c")).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("contains throws an error for invalid input like an array", () => { + expect(() => contains(["a", "b"], "a")).toThrow("Invalid input: expected an object"); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..5d8ba92d1 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,7 @@ -function createLookup() { +function createLookup(key, value) { + return Object.fromEntries(key, value); // implementation here + } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..c1952760d 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,10 @@ 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 expectedOutput = { US : 'USD', CA: 'CAD' }; + expect(createLookup(input)).toEqual(expectedOutput); +}); /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..faa136e2f 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -6,11 +6,20 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + const [key, ...rest] = pair.split("="); + const value = rest.join("="); + if (queryParams.hasOwnProperty(key)) { + if (!Array.isArray(queryParams[key])) { + queryParams[key] = [queryParams[key]]; + } + queryParams[key].push(value); + } else { queryParams[key] = value; + } } return queryParams; } + module.exports = parseQueryString; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..0c367d3d1 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -10,3 +10,28 @@ test("parses querystring values containing =", () => { "equation": "x=y+1", }); }); + +test("parses querystring with multiple values", () => { + expect(parseQueryString("a=1&b=2=c")).toEqual({ + "a": "1", + "b": "2=c", + }); +}); +test("parses querystring Multiple values for the same key", () =>{ + expect(parseQueryString("category=books&category=movies&category=music")).toEqual({ + "category": ["books", "movies", "music"] + }) +}); +test("parses querystring with empty values", () => { + expect(parseQueryString("username=")).toEqual({ + username: "" + }); +}); +test("parses querystring key with no values", () => { + expect(parseQueryString("hello=")).toEqual({ hello: "" }); +}); +test ("parses querystring with no equal sign", () => { + expect(parseQueryString("bye")).toEqual({ + bye: "" + }); +}); \ No newline at end of file diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..d6ffc4be9 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,11 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("Invalid input: expected an array"); + } + return items.reduce((acc, item) => { + acc[item] = (acc[item] || 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..80a62a5b3 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,27 @@ 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 with a single item returns count of 1", () => { + expect(tally(['a'])).toEqual({ a: 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 +test("tally with duplicate items returns correct counts", () => { + 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 like a string", () => { + expect(() => tally("not an array")).toThrow("Invalid input: expected an array"); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..525922a68 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,20 +10,38 @@ function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + // invertedObj.key = value; + invertedObj[value] = key; } return invertedObj; } +console.log(invert({ a: 1, b: 2 })); // a) What is the current return value when invert is called with { a : 1 } +// Object.entries({ a: 1 }) gives [['a', 1]] +// so in the loop key = "a", value = 1 +// the line invertedObj.key = value; it adds a property with the key "key" rather than using the variable key. +// { key: 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } +// {key: 2} it overwrites the previous value of key + // c) What is the target return value when invert is called with {a : 1, b: 2} +// we want to change the position of the key and value +// {"1": "a", "2": "b} // c) What does Object.entries return? Why is it needed in this program? +// it returns the object in to array +// Object.entries ({a: 1, b: 2}) returns +// [["a", 1], ["b", 2]] // d) Explain why the current return value is different from the target output +// this line invertedObj.key = value; +// it is not using the key variable it is adding a property named key. // e) Fix the implementation of invert (and write tests to prove it's fixed!) + + +module.exports = invert; \ No newline at end of file diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..4b90bd1c2 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,24 @@ +const invert = require('./invert'); +test('invert a single key-value pair', () => { + expect(invert({ a: 1 })).toEqual({ '1': 'a' }); +}); + + +test('invert multiple key-value pairs', () => { + expect(invert({ a: 1, b: 2 })).toEqual({ '1': 'a', '2': 'b' }); +}); + + +test("invert string value as key input ", () => { + expect(invert({ a: "apple", b: "banana" })).toEqual({ apple: "a", banana: "b" }); +}); + + +test ("returns empty object when input is empty", () => { + expect(invert({})).toEqual({}); +}); + +test("invert with numeric keys", () => { + expect(invert({ 1: 'one', 2: 'two' })).toEqual({ one: '1', two: '2' }); +}); + diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..e925fafda 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,17 @@ 3. Order the results to find out which word is the most common in the input */ +function countWords(str) { + const cleanStr = str.replace(/[.,!?]/g, "").toLowerCase(); + const words = cleanStr.split(" "); + const count = words.reduce((acc, word) => { + acc[word] = (acc[word] || 0) + 1; + return acc; + }, {}); + const sorted = Object.entries(count).sort((a, b) => b[1] - a[1]); + return sorted; +} + + +console.log(countWords("you and me and you")); + diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..1601c9883 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,22 +8,21 @@ // 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; + function getFrequencies(list) { + const freq = new Map(); + for (let num of list) { + if (typeof num !== "number") continue; + freq.set(num, (freq.get(num) || 0) + 1); } - - freqs.set(num, (freqs.get(num) || 0) + 1); + return freq; } // Find the value with the highest frequency + function getMode(freqMap) { let maxFreq = 0; let mode; - for (let [num, freq] of freqs) { + for (let [num, freq] of freqMap) { if (freq > maxFreq) { mode = num; maxFreq = freq; @@ -32,5 +31,8 @@ function calculateMode(list) { return maxFreq === 0 ? NaN : mode; } - + function calculateMode(list) { + const freq = getFrequencies(list); + return getMode(freq); +} module.exports = calculateMode; diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..09404e6a3 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -8,10 +8,14 @@ function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + const coinValue = parseInt(coin); + if (isNaN(coinValue)) { + continue; + } + total += coinValue * quantity; } - return `£${total / 100}`; + return `£${(total / 100).toFixed(2)}`; } const till = { @@ -21,11 +25,21 @@ const till = { "20p": 10, }; const totalAmount = totalTill(till); +console.log(totalAmount); +module.exports = totalTill; // a) What is the target output when totalTill is called with the till object +// 10p + 30p + 200p + 200p = 440p → £4.40 so the target output is "£4.40" // b) Why do we need to use Object.entries inside the for...of loop in this function? +// because till os an object and if we use Object.entries, it will return an array of key-value pairs. +// if we don't use Object.entries, we would either get the keys (with Object.keys) or only the values (with object.values), but not both // c) What does coin * quantity evaluate to inside the for...of loop? - +//The expression coin * quantity tries to multiply a string (coin) by a number (quantity). +// so coin * quantity evaluates to NaN because coin is a string like "50p", and JavaScript cannot multiply a non-numeric string by a number. +// to fix that we need to extract the number from "50p" +// parseInt(coin) will convert the string "50p" to 50, and then we can multiply it by quantity. +// total += parseInt(coin) * quantity; // 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..bff0a04be --- /dev/null +++ b/Sprint-2/stretch/till.test.js @@ -0,0 +1,42 @@ +const totalTill = require("./till"); + + +describe("totalTill()", () => { + test("calculates the correct total from the till", () => { + const till = { + "1p": 10, // 10p + "5p": 6, + "50p": 4, // 200p + "20p": 10 // 200p + }; + const result = totalTill(till); + expect(result).toBe("£4.40"); + }); + + + test("returns £0.00 for an empty till", () => { + expect(totalTill({})).toBe("£0.00"); + }); + test("ignores non-numeric keys", () => { + const till = { + "20p" : 10, + "?p": 5 + }; + expect(totalTill(till)).toBe("£2.00"); + }); + test("handles large quantities correctly", () => { + const till = { + "1p": 10000, + "5p": 2000, + }; + expect(totalTill(till)).toBe("£200.00"); + }); + test("works with the single coin type", () => { + const till = { + "20p": 3, + }; + expect(totalTill(till)).toBe("£0.60"); + }); + + + }); \ No newline at end of file