diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..12ba4536b 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,8 +1,10 @@ -// Predict and explain first... +/* Predict and explain first... -// This code should log out the houseNumber from the address object -// but it isn't working... -// Fix anything that isn't working +My prediction is that when I run this code it will return undefined. +This is because this is an object not an array, objects are not indexed by numbers, they are indexed by keys. +So when i try to access address[0], it will return undefined because there is no key '0' in the address object. + +*/ const address = { houseNumber: 42, @@ -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..824802570 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,7 +1,8 @@ -// Predict and explain first... - -// 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 +/* Predict and explain first... +I think this will return an error because of the for...of loop. +The for...of loop is used to iterate over iterable objects like arrays, strings, maps, sets, etc. +So this will return an error because the author object is not iterable. +*/ 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..738dfdf1d 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,8 +1,10 @@ -// Predict and explain first... +/* Predict and explain first... -// 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? +This code will log the title and serves correctly, but the ingredients will not be logged on separate lines as intended. +This happens because the ingredients array is being logged as a whole object, which will not format it correctly. +The output will for the ingredients will be [object Object] instead of each ingredient on a new line. + + */ const recipe = { title: "bruschetta", @@ -11,5 +13,7 @@ const recipe = { }; console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); + ingredients:`); +recipe.ingredients.forEach((ingredient) => { + console.log(`- ${ingredient}`); +}); diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..0e164de6d 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,7 @@ -function contains() {} - +function contains(obj, prop) { + if (typeof obj !== "object" || obj === null) { + return false; + } + return obj.hasOwnProperty(prop); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..9ff5a769b 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,33 @@ 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"); - -// Given an object with properties -// When passed to contains with an existing property name -// Then it should return true - -// Given an object with properties -// When passed to contains with a non-existent property name -// Then it should return false - -// Given invalid parameters like an array -// When passed to contains -// Then it should return false or throw an error +describe("contains", () => { + test("return false when passed an empty object", () => { + expect(contains({}, "a")).toEqual(false); + }); + + // Given an object with properties + // When passed to contains with an existing property name + // Then it should return true + test("return true when passed an object with an existing property name", () => { + expect(contains({ a: 1, b: 2 }, "a")).toEqual(true); + expect(contains({ apple: 1, banana: 2 }, "banana")).toEqual(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 passed an object with a non-existent property name", () => { + expect(contains({ a: 1, b: 2 }, "c")).toEqual(false); + expect(contains({ apple: 1, banana: 2 }, "orange")).toEqual(false); + }); + + // Given invalid parameters like an array + // When passed to contains + // Then it should return false or throw an error + test("return false when passed invalid parameters like an array", () => { + expect(contains([], "a")).toEqual(false); + expect(contains(null, "a")).toEqual(false); + expect(contains(undefined, "a")).toEqual(false); + }); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..b082d3152 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,13 @@ -function createLookup() { - // implementation here +function createLookup(countryCurrencyPairs) { + if (!Array.isArray(countryCurrencyPairs)) { + throw new Error("Invalid input"); + } + + const lookup = {}; + for (const [country, currency] of countryCurrencyPairs) { + 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..63cc12510 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,43 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +describe("createLookup", () => { + test("creates a country currency code lookup for a single code", () => { + const countryCurrencyPairs = [["US", "USD"]]; + const expectedLookup = { US: "USD" }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); + }); + + test("creates a country currency code lookup for multiple codes", () => { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + const expectedLookup = { US: "USD", CA: "CAD" }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); + }); + + test("returns an empty object when passed an empty array", () => { + const countryCurrencyPairs = []; + const expectedLookup = {}; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); + }); + + test("throws error for invalid input", () => { + expect(() => createLookup(null)).toThrow(); + expect(() => createLookup(undefined)).toThrow(); + expect(() => createLookup({})).toThrow(); + }); + + test("returns repeated keys with the last value", () => { + const countryCurrencyPairs = [ + ["US", "USN"], + ["CA", "CAD"], + ["US", "USD"], + ]; + const expectedLookup = { US: "USD", CA: "CAD" }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); + }); +}); /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..7cc029333 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -4,9 +4,18 @@ function parseQueryString(queryString) { return queryParams; } const keyValuePairs = queryString.split("&"); + const replacePlusWithSpace = (str) => str.replace(/\+/g, " "); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + if (pair === "") continue; + + const [rawKey, ...rawValueParts] = pair.split("="); + const keyWithSpaces = replacePlusWithSpace(rawKey || ""); + const valueWithSpaces = replacePlusWithSpace(rawValueParts.join("=") || ""); + + const key = decodeURIComponent(keyWithSpaces); + const value = decodeURIComponent(valueWithSpaces); + queryParams[key] = value; } diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 328b8df61..4d4238f74 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,7 +3,7 @@ // Below are some test cases the implementation doesn't handle well. // Fix the implementation for these tests, 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("should parse values containing '='", () => { expect(parseQueryString("equation=a=b-2")).toEqual({ @@ -37,12 +37,6 @@ test("should replace '+' by ' '", () => { }); }); -// Stretch exercise: Handling query strings that contain identical keys - -// Delete this test if you are not working on this optional case -test("should store values of a key in an array when the key has 2 or more values", () => { - expect(parseQueryString("key=value1&key=value2&key=value3&foo=bar")).toEqual({ - key: ["value1", "value2", "value3"], - foo: "bar", - }); +test("should handle duplicate keys", () => { + expect(parseQueryString("a=1&a=2")).toEqual({ a: "2" }); }); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..3a794486e 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,13 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("Invalid input"); + } + + 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..3a34f75e0 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -14,21 +14,38 @@ const tally = require("./tally.js"); * 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 - // 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"); +describe("tally", () => { + test("returns an empty object for an empty array", () => { + expect(tally([])).toEqual({}); + }); -// Given an array with duplicate items -// When passed to tally -// Then it should return counts for each unique item + // Given an array with duplicate items + // When passed to tally + // Then it should return counts for each unique item + test("returns counts for each unique item", () => { + expect(tally(["apple"])).toEqual({ apple: 1 }); + expect(tally(["apple", "banana", "orange"])).toEqual({ + apple: 1, + banana: 1, + orange: 1, + }); + }); -// Given an invalid input like a string -// When passed to tally -// Then it should throw an error + test("returns counts for each unique item with duplicates", () => { + expect( + tally(["apple", "banana", "orange", "apple", "banana", "orange"]) + ).toEqual({ apple: 2, banana: 2, orange: 2 }); + }); + + // Given an invalid input like a string + // When passed to tally + // Then it should throw an error + test("throws an error for invalid input", () => { + expect(() => tally("invalid input")).toThrow("Invalid input"); + expect(() => tally(123)).toThrow("Invalid input"); + expect(() => tally({})).toThrow("Invalid input"); + }); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..3b736368c 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,7 +6,7 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} -function invert(obj) { +/*function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { @@ -14,16 +14,38 @@ function invert(obj) { } 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 key-value pairs from the object. It's needed to iterate over the object's properties. // d) Explain why the current return value is different from the target output +/*The current return value is different from the target output because the code is incorrectly using `invertedObj.key` +instead of using the actual key and value from the object. It should be using `invertedObj[value] = key;` to correctly +swap the keys and values.*/ // e) Fix the implementation of invert (and write tests to prove it's fixed!) +function invert(obj) { + const invertedObj = {}; + + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + throw new Error("Invalid input"); + } + + for (const [key, value] of Object.entries(obj)) { + invertedObj[value] = key; + } + + return invertedObj; +} + +module.exports = invert; diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..263fd676c --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,24 @@ +const invert = require("./invert"); + +describe("invert", () => { + test("returns an empty object for an empty object", () => { + expect(invert({})).toEqual({}); + }); + + test("returns inverted object for a single key-value pair", () => { + expect(invert({ a: 1 })).toEqual({ 1: "a" }); + }); + + test("returns inverted object for multiple key-value pairs", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ 1: "a", 2: "b" }); + }); + + test("throws an error for invalid input", () => { + expect(() => invert(null)).toThrow("Invalid input"); + expect(() => invert("Number")).toThrow("Invalid input"); + expect(() => invert(123)).toThrow("Invalid input"); + }); + test("returns last key for duplicate values", () => { + expect(invert({ a: 1, b: 2, c: 1 })).toEqual({ 1: "c", 2: "b" }); + }); +}); diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..d409c7f27 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,20 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(str) { + if (typeof str !== "string") { + throw new Error("Invalid input"); + } + + str = str.replace(/[.,!?]/g, ""); + str = str.toLowerCase(); + const words = str.split(" ").filter(Boolean); + const result = {}; + + for (const word of words) { + result[word] = (result[word] || 0) + 1; + } + const sortedResult = Object.entries(result).sort((a, b) => b[1] - a[1]); + return sortedResult; +} diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..ad468d0b6 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,19 +8,19 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { +function countFrequencies(list) { // track frequency of each value 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); } + return freqs; +} - // Find the value with the highest frequency +// Find the value with the highest frequency +function findMode(freqs) { let maxFreq = 0; let mode; for (let [num, freq] of freqs) { @@ -29,8 +29,12 @@ function calculateMode(list) { maxFreq = freq; } } - return maxFreq === 0 ? NaN : mode; } +function calculateMode(list) { + const freqs = countFrequencies(list); + return findMode(freqs); +} + module.exports = calculateMode; diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..de691362e 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -4,7 +4,7 @@ // When this till object is passed to totalTill // Then it should return the total amount in pounds -function totalTill(till) { +/*function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { @@ -20,12 +20,34 @@ const till = { "50p": 4, "20p": 10, }; -const totalAmount = totalTill(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? +// Object.entries is needed because it gives us each coin and its quantity as a pair, allowing the for…of loop to work with both values together. // c) What does coin * quantity evaluate to inside the for...of loop? +//Inside the loop, coin * quantity evaluates to NaN because coin is a string like "1p", and JavaScript cannot multiply a string by a number. // d) Write a test for this function to check it works and then fix the implementation of totalTill +function totalTill(till) { + let total = 0; + + for (const [coin, quantity] of Object.entries(till)) { + const valueInPence = parseInt(coin); + total += valueInPence * quantity; + } + + return `£${total / 100}`; +} + +const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, +}; +const totalAmount = totalTill(till); +module.exports = totalTill; diff --git a/Sprint-2/stretch/till.test.js b/Sprint-2/stretch/till.test.js new file mode 100644 index 000000000..dfaa8f3d4 --- /dev/null +++ b/Sprint-2/stretch/till.test.js @@ -0,0 +1,16 @@ +const totalTill = require("./till.js"); + +describe("totalTill()", () => { + test("calculates total amount in pounds", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + }; + + const result = totalTill(till); + + expect(result).toBe("£4.4"); + }); +});