diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..36d82085a 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,4 +1,6 @@ // Predict and explain first... +// As this is an object (key-value pairs), the elements cannot be accessed through indexing like array. +// Values can be accessed through their relevant key // 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..622620136 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,4 +1,6 @@ // Predict and explain first... +// As object is key-value pairs of data, it isn't exactly iterable like arrays. +// We can loop over the keys or values of an object using Object.entries() // 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 +13,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const [key, value] of Object.entries(author)) { console.log(value); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..bd012af67 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,4 +1,5 @@ // Predict and explain first... +// It is not accessing the ingredients array correctly. // 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 +13,17 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); + + +// approach 2 + +// let msg = ''; + +// for (const ingredient of recipe.ingredients) { +// msg += ingredient + '\n'; +// } + +// console.log(`${recipe.title} serves ${recipe.serves} +// ingredients: +// ${msg}`); \ No newline at end of file diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..977db9e6c 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,13 @@ -function contains() {} +function contains(obj, x) { + for (const key in obj) { + if (Object.hasOwn(obj, key) && key == x) { + return true + } + } + return false +} module.exports = contains; + +// Object.hasOwn(obj, key) +// This ensures the property belongs directly to the object, not its prototype. \ No newline at end of file diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..8d1bf7179 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,38 @@ 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({})).toStrictEqual(false) +}); + // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("contains on object with an existing property name returns true", () => { + expect(contains({a: 1, b: 2, c: 3}, 'c')).toStrictEqual(true); + expect(contains({name: "John Doe", job: "developer"}, 'job')).toStrictEqual(true) + expect(contains({color: "blue", size: "xxl", madeOf: "China"}, 'madeOf')).toStrictEqual(true); +}) + // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains on object with properties with non-existent property name return false", () => { + expect(contains({a: 1, b: 2, c: 3}, 'g')).toStrictEqual(false); + expect(contains({a: 1, b: 2, c: 3}, 'abc')).toStrictEqual(false); + expect(contains({name: "John Doe", age: 31}, 'job')).toStrictEqual(false); +}) + // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error + +test("contains on invalid parameters returns false", () => { + expect(contains(["a", "b", "c"])).toStrictEqual(false); + expect(contains("I am a string")).toStrictEqual(false); + expect(contains(777)).toStrictEqual(false); +}) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..c1911e518 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,5 @@ -function createLookup() { - // implementation here +function createLookup(arr) { + return Object.fromEntries(arr); } -module.exports = createLookup; +module.exports = createLookup; \ No newline at end of file diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..88517abf9 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,20 @@ 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 givenInput = createLookup(input); + const targetOutput = {US: 'USD', CA: 'CAD'}; + + expect(givenInput).toEqual(targetOutput); +}); + +test("creates a country currency code lookup for multiple codes", () => { + const input = [['AUS', 'AUD'], ['GB', 'GBP'], ['US', 'USD']]; + const givenInput = createLookup(input); + const targetOutput = {AUS: 'AUD', GB: 'GBP', US: 'USD'}; + + expect(givenInput).toEqual(targetOutput); +}) /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..8eff0a700 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,12 +1,23 @@ function parseQueryString(queryString) { const queryParams = {}; - if (queryString.length === 0) { - return queryParams; - } + + if (!queryString) return queryParams; + const keyValuePairs = queryString.split("&"); + // 1. with RegEx + // for (const pair of keyValuePairs) { + // const [key, value] = pair.split(/=(.*)/s); + // queryParams[key] = value; + // } + + // 2. with substring method for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + const index = pair.indexOf("="); + + const key = pair.substring(0, index); + const value = pair.substring(index + 1); + queryParams[key] = value; } diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..aadfbc025 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -9,4 +9,27 @@ test("parses querystring values containing =", () => { expect(parseQueryString("equation=x=y+1")).toEqual({ "equation": "x=y+1", }); + expect(parseQueryString("token=abc=123=xyz")).toEqual({ + "token": "abc=123=xyz", + }); + expect(parseQueryString("shop=myshop.myshopify.com")).toEqual({ + "shop": "myshop.myshopify.com", + }); +}); + +test("parses querystring values containing = and &", () => { + expect(parseQueryString("category=shoes&color=black&size=10")).toEqual({ + category: "shoes", color: "black", size: "10" + }); + expect(parseQueryString("page=2&limit=10")).toEqual({ + page: "2", limit: "10" + }); +}); + +test("parses querystring values containing +", () => { + expect(parseQueryString("q=javascript+array+methods")).toEqual({ + q: "javascript+array+methods" + }) }); + + diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..c685bcc23 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,26 @@ -function tally() {} +function tally(arr) { + if (Array.isArray(arr)) { + + let countObj = {}; + + for (const item of arr) { + + if (countObj[item]) { + countObj[item] = countObj[item] + 1; + } else { + countObj[item] = 1; + } + } + + return countObj; + } + + else throw new Error(""); +} module.exports = tally; + +// console.log(tally(['a', 'a', 'a', 'a', 'a', 'b', 'c'])); + + +// tally(['a', 'a', 'a', 'a', 'a', 'b', 'c']), target output: { a : 5, b: 1, c: 1 } \ No newline at end of file diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..e9870bbaa 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,38 @@ 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 the counts for each uniqe item", () => { + const input = ['a', 'a', 'b', 'c']; + const givenInput = tally(input); + const targetOutput = { a : 2, b: 1, c: 1 }; + + expect(givenInput).toEqual(targetOutput); +}) + +test("tally on an array with duplicate items returns the counts for each uniqe item", () => { + const input = ['a', 'a', 'bx', 'bx', 'b', 'b', 'b', 'b', 'c']; + const givenInput = tally(input); + const targetOutput = { a : 2, bx: 2, b: 4, c: 1 }; + + expect(givenInput).toEqual(targetOutput); +}) + // Given an invalid input like a string // When passed to tally // Then it should throw an error + +test("tall on invalid input throws an error", () => { + expect(() => tally("I am a string")).toThrow(); + expect(() => tally(777)).toThrow(); + expect(() => tally(true)).toThrow(); + expect(() => tally(undefined)).toThrow(); + expect(() => tally(null)).toThrow(); +}) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..aa2989bf5 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,20 +10,29 @@ function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; } 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} + // { key: 2 } // 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 here in order to loop over each of the key-value pairs // d) Explain why the current return value is different from the target output + // It is because `.key `is used on object. + // Instead [] should be used // e) Fix the implementation of invert (and write tests to prove it's fixed!) + // fixed the issue by using `invertedObj[value] = key` + // It is swaping the key and value in the returned object \ No newline at end of file diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..d5ff6209c 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,26 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(str) { + const arr = str.replace(/[^a-zA-Z0-9\s]/g, '').toLowerCase().split(" ") + + let countObj = {}; + + for (const item of arr) { + + if (countObj[item]) { + countObj[item] = countObj[item] + 1 + } else { + countObj[item] = 1; + } + } + + // `Object.entries()` converts object to array + const sortedArray = Object.entries(countObj).sort((a, b) => b[1] - a[1]); + + // `Object.fromEntries()` coverts the sorted array to object + return Object.fromEntries(sortedArray); +} + +console.log(countWords("you? and! me, and you. me me me me hello Me")); diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..90f0087d5 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,7 +8,7 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { +function calculateFrequency(list) { // track frequency of each value let freqs = new Map(); @@ -19,6 +19,11 @@ function calculateMode(list) { freqs.set(num, (freqs.get(num) || 0) + 1); } + return freqs; +} + +function calculateMode(list) { + const freqs = calculateFrequency(list); // Find the value with the highest frequency let maxFreq = 0; diff --git a/Sprint-2/stretch/mode.test.js b/Sprint-2/stretch/mode.test.js index ca33c28a3..a95ad7036 100644 --- a/Sprint-2/stretch/mode.test.js +++ b/Sprint-2/stretch/mode.test.js @@ -29,4 +29,11 @@ describe("calculateMode()", () => { expect(calculateMode(nums)).toEqual(3); }); + + test("ignores non-number values", () => { + const nums = "CYF"; + + expect(calculateMode(nums)).toEqual(NaN); + }); + }); diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..af34480ef 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -8,10 +8,10 @@ function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + total += Number(coin.slice(0, -1)) * Number(quantity); } - return `£${total / 100}`; + return `£${(total / 100).toFixed(2)}`; } const till = { @@ -22,10 +22,15 @@ const till = { }; const totalAmount = totalTill(till); +module.exports = totalTill; + // a) What is the target output when totalTill is called with the till object + // £NaN // b) Why do we need to use Object.entries inside the for...of loop in this function? + // to convert the given object into array // c) What does coin * quantity evaluate to inside the for...of loop? + // it converts different coins with different values into single pense // 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..e99695680 --- /dev/null +++ b/Sprint-2/stretch/till.test.js @@ -0,0 +1,29 @@ +const totalTill = require('./till'); + +test("given an object of coins, returns the total amount in pounds", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + }; + const givenInput = totalTill(till); + const expectedOutcome = "£4.40"; + + expect(givenInput).toEqual(expectedOutcome); +}); + +test("given an object of coins, returns the total amount in pounds", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + "2p": 2 + }; + const givenInput = totalTill(till); + const expectedOutcome = "£4.44"; + + expect(givenInput).toEqual(expectedOutcome); +}); +