diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index 046be3d00..6611ca3a4 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -2,10 +2,42 @@ // Start by running the tests for this function // If you're in the week-1 directory, you can run npm test -- fix to run the tests in the fix directory +// Old Code: + +// function calculateMedian(list) { +// const middleIndex = Math.floor(list.length / 2); +// const median = list.splice(middleIndex, 1)[0]; +// return median; +// } + +// Explanation: +// The original implementation has several mistakes: +// 1 - .splice() mutates or changes the original array. We don't want this to happen. +// 2 - We actually don't need .splice() or any other method to find a median. +// 3 - We must sort the array before we search for the median. +// 4 - We should check if the 'list' parameter actually has numbers, otherwise try to filter +// out all non-numeric values and throw an error or return null if there are no numeric values. + + +// Example of a function calculating median and returning null in case no numeric values provided. + function calculateMedian(list) { - const middleIndex = Math.floor(list.length / 2); - const median = list.splice(middleIndex, 1)[0]; - return median; + if (!Array.isArray(list)) { + return null; + } + const onlyNumberList = list.filter(item => typeof item === 'number' && !isNaN(item)); + + if (!onlyNumberList.length) { + return null; + } else { + const sortedList = onlyNumberList.sort((current, next) => current - next); + const indexNearMiddleOfArray = Math.floor(sortedList.length / 2); + if (sortedList.length % 2 === 0) { + return (sortedList[indexNearMiddleOfArray - 1] + sortedList[indexNearMiddleOfArray]) / 2; + } else { + return sortedList[indexNearMiddleOfArray]; + } + } } module.exports = calculateMedian; diff --git a/Sprint-1/fix/median.test.js b/Sprint-1/fix/median.test.js index 11bec6fcc..16643a163 100644 --- a/Sprint-1/fix/median.test.js +++ b/Sprint-1/fix/median.test.js @@ -7,20 +7,42 @@ const calculateMedian = require("./median.js"); describe("calculateMedian", () => { - test("returns the median for odd length array", () => { - expect(calculateMedian([1, 2, 3])).toBe(2); - expect(calculateMedian([1, 2, 3, 4, 5])).toBe(3); - }); + [ + { input: [1, 2, 3], expected: 2 }, + { input: [1, 2, 3, 4, 5], expected: 3 }, + { input: [1, 2, 3, 4], expected: 2.5 }, + { input: [1, 2, 3, 4, 5, 6], expected: 3.5 }, + ].forEach(({ input, expected }) => + it(`returns the median for [${input}]`, () => expect(calculateMedian(input)).toEqual(expected)) + ); - test("returns the average of middle values for even length array", () => { - expect(calculateMedian([1, 2, 3, 4])).toBe(2.5); - expect(calculateMedian([1, 2, 3, 4, 5, 6])).toBe(3.5); - }); + [ + { input: [3, 1, 2], expected: 2 }, + { input: [5, 1, 3, 4, 2], expected: 3 }, + { input: [4, 2, 1, 3], expected: 2.5 }, + { input: [6, 1, 5, 3, 2, 4], expected: 3.5 }, + ].forEach(({ input, expected }) => + it(`returns the correct median for unsorted array [${input}]`, () => expect(calculateMedian(input)).toEqual(expected)) + ); - test("doesn't modify the input", () => { + it("doesn't modify the input array [1, 2, 3]", () => { const list = [1, 2, 3]; calculateMedian(list); - expect(list).toEqual([1, 2, 3]); }); -}); + + [ 'not an array', 123, null, undefined, {}, [], ["apple", null, undefined] ].forEach(val => + it(`returns null for non-numeric array (${val})`, () => expect(calculateMedian(val)).toBe(null)) + ); + + [ + { input: [1, 2, "3", null, undefined, 4], expected: 2 }, + { input: ["apple", 1, 2, 3, "banana", 4], expected: 2.5 }, + { input: [1, "2", 3, "4", 5], expected: 3 }, + { input: [1, "apple", 2, null, 3, undefined, 4], expected: 2.5 }, + { input: [3, "apple", 1, null, 2, undefined, 4], expected: 2.5 }, + { input: ["banana", 5, 3, "apple", 1, 4, 2], expected: 3 }, + ].forEach(({ input, expected }) => + it(`filters out non-numeric values and calculates the median for [${input}]`, () => expect(calculateMedian(input)).toEqual(expected)) + ); +}); \ No newline at end of file diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js index 781e8718a..858f8e5e6 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1 +1,10 @@ -function dedupe() {} +// Explanation: + +// All elements in a set are unique, so creating a set from an array removes duplicates - new Set(list) +// Creating a new array and using the spread operator (3 dots, ...) will destruct the values of the set back to the array + +function dedupe(list) { + return [...new Set(list)]; +} + +module.exports = dedupe; diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index 145f44407..f89e25a35 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -15,7 +15,6 @@ E.g. dedupe([5, 1, 1, 2, 3, 2, 5, 8]) target output: [5, 1, 2, 3, 8] // Given an empty array // When passed to the dedupe function // Then it should return an empty array -test.todo("given an empty array, it returns an empty array"); // Given an array with no duplicates // When passed to the dedupe function @@ -24,3 +23,20 @@ test.todo("given an empty array, it returns an empty array"); // Given an array with strings or numbers // When passed to the dedupe function // Then it should remove the duplicate values + +describe("dedupe", () => { + it("returns the array without duplicates", () => { + expect(dedupe(['a','a','a','b','b','c'])).toStrictEqual(['a','b','c']); + expect(dedupe([5, 1, 1, 2, 3, 2, 5, 8])).toStrictEqual([5, 1, 2, 3, 8]); + }); + + it("returns the original array when the original array does not contain duplicates", () => { + expect(dedupe(['a','b','c'])).toStrictEqual(['a','b','c']); + expect(dedupe([5, 1, 2, 3, 8])).toStrictEqual([5, 1, 2, 3, 8]); + }); + + it("returns an array when an empty array is passed to the function", () => { + expect(dedupe([])).toStrictEqual([]); + }); +}); + diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index e69de29bb..93e0918b3 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -0,0 +1,17 @@ +// Explanation: + +// list.filter(value => typeof value === "number") checks the value type (number, string etc) +// to remove anything that is not a number +// numericValues.length === 0 ? -Infinity : Math.max(...numericValues) is a ternary operator expression +// if the condition (whatever is before the question mark, in this case (numericValues.length === 0)) is true +// then -Infinity will be returned, if the condition is false Math.max(...numericValues) will be returned +// Math.Max() finds the largest number in the given parameters + + +function getLargestNumber(list) { + const numericValues = list.filter(value => typeof value === "number"); + + return numericValues.length === 0 ? -Infinity : Math.max(...numericValues); +} + +module.exports = getLargestNumber; diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index b1ba25562..9a0a27a4c 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -9,6 +9,7 @@ E.g. max(['hey', 10, 'hi', 60, 10]), target output: 60 (sum ignores any non-nume // Given an empty array // When passed to the max function // Then it should return -Infinity + test.todo("given an empty array, returns -Infinity"); // Given an array with one number @@ -26,3 +27,27 @@ test.todo("given an empty array, returns -Infinity"); // Given an array with non-number values // When passed to the max function // Then it should return the max and ignore non-numeric values + +const getLargestNumber = require("./max"); + +describe("getLargestNumber", () => { + it("returns negative infinity when the array is empty", () => { + expect(getLargestNumber([])).toEqual(-Infinity); + }); + + it("returns the only value when the array contains only 1 number", () => { + expect(getLargestNumber([5])).toEqual(5); + }); + + it("returns the largest number when the array contains positive and negative numbers", () => { + expect(getLargestNumber([43, 75, 21, -64, -97])).toEqual(75); + }); + + it("returns the largest number when the array contains strings, and positive and negative numbers", () => { + expect(getLargestNumber([-54, 71, 342, -987321, "test", "again"])).toEqual(342); + }); + + it("returns the largest decimal number when the array contains decimal numbers", () => { + expect(getLargestNumber([14.7, 1.43, 87.45, 6.432])).toEqual(87.45); + }); +}); diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index e69de29bb..2caa520c8 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -0,0 +1,12 @@ +// Explanation: + +// list.filter(value => typeof value === "number") checks the value type (number, string etc) +// to remove anything that is not a number +// reduce((agg, value) => agg + value, 0) uses an aggregator (agg) to keep an overall tally while iterating the array +// the second argument in the reduce function, in this case a 0, is the default value of the aggregator + +function sum(list) { + return list.filter(value => typeof value === "number").reduce((agg, value) => agg + value, 0); +} + +module.exports = sum; diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index 6b623592c..c7246e771 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -27,3 +27,27 @@ E.g. sum(['hey', 10, 'hi', 60, 10]), target output: 80 (ignore any non-numerical // Given an array containing non-number values // When passed to the sum function // Then it should ignore the non-numerical values and return the sum of the numerical elements + +const sum = require("./sum"); + +describe("sum", () => { + it("returns 0 when the array is empty", () => { + expect(sum([])).toEqual(0); + }); + + it("returns the only number when the array contains one number", () => { + expect(sum([8])).toEqual(8); + }); + + it("returns the correct sum value when the array contains a sequence of numbers", () => { + expect(sum([-10, 40, 50])).toEqual(80); + expect(sum([10, 20, 30])).toEqual(60); + expect(sum([1.1, 2.2, 3.3])).toEqual(6.6); + }); + + it("returns the correct sum value when the array contains a sequence of numbers and strings", () => { + expect(sum([-10, 40, 50, "testing"])).toEqual(80); + expect(sum([10, 20, 30, "testing"])).toEqual(60); + expect(sum([1.1, 2.2, 3.3, "testing"])).toEqual(6.6); + }); +}); \ No newline at end of file diff --git a/Sprint-1/refactor/find.js b/Sprint-1/refactor/find.js deleted file mode 100644 index 7df447b9c..000000000 --- a/Sprint-1/refactor/find.js +++ /dev/null @@ -1,13 +0,0 @@ -// Refactor the implementation of find to use a for...of loop - -function find(list, target) { - for (let index = 0; index < list.length; index++) { - const element = list[index]; - if (element === target) { - return index; - } - } - return -1; -} - -module.exports = find; diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js new file mode 100644 index 000000000..3740fa92c --- /dev/null +++ b/Sprint-1/refactor/includes.js @@ -0,0 +1,23 @@ +// Refactor the implementation of find to use a for...of loop + +// function includes(list, target) { +// for (let index = 0; index < list.length; index++) { +// const element = list[index]; +// if (element === target) { +// return index; +// } +// } +// return -1; +// } + +function includes(list, target) { + for (const value of list) { + if (value === target) { + return list.indexOf(value); + } + } + + return -1; +} + +module.exports = includes; diff --git a/Sprint-1/refactor/find.test.js b/Sprint-1/refactor/includes.test.js similarity index 61% rename from Sprint-1/refactor/find.test.js rename to Sprint-1/refactor/includes.test.js index b5c35deb9..37317be43 100644 --- a/Sprint-1/refactor/find.test.js +++ b/Sprint-1/refactor/includes.test.js @@ -1,37 +1,37 @@ -// Refactored version of find should still pass the tests below: +// Refactored version of includes should still pass the tests below: -const find = require("./find.js"); +const includes = require("./includes.js"); test("returns index when target is in array", () => { - const currentOutput = find(["a", "b", "c", "d"], "c"); + const currentOutput = includes(["a", "b", "c", "d"], "c"); const targetOutput = 2; expect(currentOutput).toBe(targetOutput); }); test("returns -1 when target not in array", () => { - const currentOutput = find([1, 2, 3, 4], "a"); + const currentOutput = includes([1, 2, 3, 4], "a"); const targetOutput = -1; expect(currentOutput).toBe(targetOutput); }); test("returns index of first match", () => { - const currentOutput = find([1, 2, 2, 3], 2); + const currentOutput = includes([1, 2, 2, 3], 2); const targetOutput = 1; expect(currentOutput).toBe(targetOutput); }); test("returns -1 for empty array", () => { - const currentOutput = find([]); + const currentOutput = includes([]); const targetOutput = -1; expect(currentOutput).toBe(targetOutput); }); test("searches for null", () => { - const currentOutput = find(["b", "z", null, "a"], null); + const currentOutput = includes(["b", "z", null, "a"], null); const targetOutput = 2; expect(currentOutput).toBe(targetOutput); diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..1800efefe 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -12,4 +12,17 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +// You can't get values from objects using indexes, you need to reference a key in the object + +// To reference object key you can have two notations: + +// object.key +// object['key'] + +// Bracket notation is useful when your property name has spaces or it is a variable. + +console.log(`My house number is ${address.houseNumber}`); + +// or + +console.log(`My house number is ${address['houseNumber']}`); diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..42cfc2ff9 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -11,6 +11,8 @@ const author = { alive: true, }; -for (const value of author) { +// You can't iterate the fields in an object, but you can extract the values to an array and then iterate + +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..72b039838 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -10,6 +10,11 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +// \n inserts a newline when logging a string, you could also put another console.log('Ingredients:') on the next line +// similiar to how I logged the ingredients + +console.log(`${recipe.title} serves ${recipe.serves} \nIngredients: `); + +for (const ingredient of recipe.ingredients) { + console.log(ingredient); +} diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..486f9abf3 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,12 @@ -function contains() {} +// Explanation: + +// !Array.isArray(object) checks if the first parameter is an array, the exclamation mark flips the condition result +// meaning if we pass an object like we are supposed to, the condition will be true instead of false +// (&&) is used to join two conditions, so both need to be true for true to be returned by the function. +// object.hasOwnProperty(key) just checks if the key exists in the object. + +function contains(object, key) { + return !Array.isArray(object) && object.hasOwnProperty(key); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index e75984b80..ba65c7a61 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -1,5 +1,3 @@ -const contains = require("./contains.js"); - /* Implement a function called contains that checks an object contains a particular property @@ -32,3 +30,23 @@ as the object doesn't contains a key of 'c' // Given invalid parameters like arrays // When passed to contains // Then it should return false or throw an error + +const contains = require("./contains.js"); + +describe("contains", () => { + it("returns true when the object contains the key specified", () => { + expect(contains({ a: 1, b: 2 }, "a")).toBeTruthy(); + }); + + it("returns false when the object does not contain the key specified", () => { + expect(contains({ a: 1, b: 2 }, "c")).toBeFalsy(); + }); + + it("returns false when the object is empty", () => { + expect(contains({}, "c")).toBeFalsy(); + }); + + it("returns false when an array is used rather than an object", () => { + expect(contains(["c"], "c")).toBeFalsy(); + }); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index 7a239939e..0d555ee5a 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,3 +1,13 @@ -function createLookup() { - // implementation here +// Explanation: + +// Reduce is used to aggregate values, in this case, acc is an object which we are using to store +// the countryCode and currencyCode key value pairs + +function createLookup(currencyCodes) { + return currencyCodes.reduce((acc, [countryCode, currencyCode]) => { + acc[countryCode] = currencyCode; + return acc; + }, {}); } + +module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 5f64d569c..0a14e3835 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,5 +1,7 @@ // ======= Test suite is provided below... ======= +const createLookup = require("./lookup"); + test("converts a single pair of currency codes", () => { expect(createLookup([["GB", "GBP"]])).toEqual({ GB: "GBP", @@ -9,7 +11,12 @@ test("converts a single pair of currency codes", () => { }); }); -test.todo("creates a country currency code lookup for multiple codes"); +test("converts a multiple pairs of currency codes", () => { + expect(createLookup([["GB", "GBP"], ["DE", "EUR"]])).toEqual({ + GB: "GBP", + DE: "EUR", + }); +}); /* diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index e69de29bb..77432df83 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -0,0 +1,12 @@ +function tally(list) { + if (!Array.isArray(list)) { + throw new Error("Invalid input: input must be an array"); + } + + return list.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 b473b750b..9cb177680 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -29,3 +29,21 @@ // Given an invalid input like a string // When passed to tally // Then it should throw an error + +const tally = require("./tally"); + +describe("tally", () => { + it("returns the correct tally for each item in the array", () => { + expect(tally(["a"])).toEqual({ a: 1 }); + expect(tally(["a", "a", "b", "b", "b", "c"])).toEqual({ a: 2, b: 3, c: 1 }); + }); + + it("returns an empty object when the array is empty", () => { + expect(tally([])).toEqual({}); + }); + + it("throws an error when the input is not an array", () => { + expect(() => tally("test")).toThrow("Invalid input: input must be an array"); + expect(() => tally(123)).toThrow("Invalid input: input must be an array"); + }); +});