diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index b22590bc6..44fafde53 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -6,9 +6,26 @@ // or 'list' has mixed values (the function is expected to sort only numbers). 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 numbers = list.filter( + (value) => typeof value === "number" && !Number.isNaN(value) + ); + + if (numbers.length === 0) { + return null; + } + + const sorted = [...numbers].sort((a, b) => a - b); + const middle = Math.floor(sorted.length / 2); + + if (sorted.length % 2 === 0) { + return (sorted[middle - 1] + sorted[middle]) / 2; + } + + return sorted[middle]; } module.exports = calculateMedian; diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js index 781e8718a..298230541 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1 +1,5 @@ -function dedupe() {} +function dedupe(array) { + return [...new Set(array)]; +} + +module.exports = dedupe; diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index d7c8e3d8e..bf09dded0 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -1,4 +1,5 @@ const dedupe = require("./dedupe.js"); + /* Dedupe Array @@ -16,13 +17,33 @@ E.g. dedupe([1, 2, 1]) returns [1, 2] // 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"); +test("given an empty array, it returns an empty array", () => { + expect(dedupe([])).toEqual([]); +}); // Given an array with no duplicates // When passed to the dedupe function // Then it should return a copy of the original array +test("given an array with no duplicates, it returns a copy of the original array", () => { + const input = [1, 2, 3]; + const result = dedupe(input); + + expect(result).toEqual([1, 2, 3]); + expect(result).not.toBe(input); +}); // Given an array of strings or numbers // When passed to the dedupe function -// Then it should return a new array with duplicates removed while preserving the +// Then it should return a new array with duplicates removed while preserving the // first occurrence of each element from the original array. +test("removes duplicate strings", () => { + expect(dedupe(["a", "a", "a", "b", "b", "c"])).toEqual(["a", "b", "c"]); +}); + +test("removes duplicate numbers", () => { + expect(dedupe([5, 1, 1, 2, 3, 2, 5, 8])).toEqual([5, 1, 2, 3, 8]); +}); + +test("preserves the first occurrence of each element", () => { + expect(dedupe([1, 2, 1])).toEqual([1, 2]); +}); diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 6dd76378e..ce2e01849 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,4 +1,9 @@ function findMax(elements) { + const numbers = elements.filter( + (element) => typeof element === "number" && !Number.isNaN(element) + ); + + return Math.max(...numbers); } module.exports = findMax; diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index 82f18fd88..75a1e7cc9 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -15,29 +15,48 @@ const findMax = require("./max.js"); // Given an empty array // When passed to the max function // Then it should return -Infinity -// Delete this test.todo and replace it with a test. -test.todo("given an empty array, returns -Infinity"); +test("given an empty array, returns -Infinity", () => { + expect(findMax([])).toBe(-Infinity); +}); // Given an array with one number // When passed to the max function // Then it should return that number +test("given an array with one number, returns that number", () => { + expect(findMax([5])).toBe(5); +}); // Given an array with both positive and negative numbers // When passed to the max function // Then it should return the largest number overall +test("returns the largest number from positive and negative numbers", () => { + expect(findMax([-10, 20, -5, 15])).toBe(20); +}); // Given an array with just negative numbers // When passed to the max function // Then it should return the closest one to zero +test("returns the largest negative number", () => { + expect(findMax([-10, -5, -20])).toBe(-5); +}); // Given an array with decimal numbers // When passed to the max function // Then it should return the largest decimal number +test("returns the largest decimal number", () => { + expect(findMax([1.5, 2.7, 2.3])).toBe(2.7); +}); // Given an array with non-number values // When passed to the max function // Then it should return the max and ignore non-numeric values +test("ignores non-number values", () => { + expect(findMax(["hey", 10, "hi", 60, 10])).toBe(60); +}); // Given an array with only non-number values // When passed to the max function // Then it should return the least surprising value given how it behaves for all other inputs +test("returns -Infinity when the array contains only non-number values", () => { + expect(findMax(["hey", null, undefined, "hi"])).toBe(-Infinity); +}); diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 9062aafe3..310a81da0 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,4 +1,7 @@ function sum(elements) { + return elements + .filter((element) => typeof element === "number" && !Number.isNaN(element)) + .reduce((total, number) => total + number, 0); } module.exports = sum; diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index dd0a090ca..63fbd6c37 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -8,29 +8,44 @@ E.g. sum(['hey', 10, 'hi', 60, 10]), target output: 80 (ignore any non-numerical const sum = require("./sum.js"); -// Acceptance Criteria: - // Given an empty array // When passed to the sum function // Then it should return 0 -test.todo("given an empty array, returns 0") +test("given an empty array, returns 0", () => { + expect(sum([])).toBe(0); +}); // Given an array with just one number // When passed to the sum function // Then it should return that number +test("given an array with one number, returns that number", () => { + expect(sum([5])).toBe(5); +}); // Given an array containing negative numbers // When passed to the sum function // Then it should still return the correct total sum +test("returns the correct sum for negative numbers", () => { + expect(sum([-5, 10, -2])).toBe(3); +}); // Given an array with decimal/float numbers // When passed to the sum function // Then it should return the correct total sum +test("returns the correct sum for decimal numbers", () => { + expect(sum([1.5, 2.5, 3])).toBe(7); +}); // 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 +test("ignores non-number values", () => { + expect(sum(["hey", 10, "hi", 60, 10])).toBe(80); +}); // Given an array with only non-number values // When passed to the sum function // Then it should return the least surprising value given how it behaves for all other inputs +test("returns 0 when there are only non-number values", () => { + expect(sum(["hey", null, undefined, "hi"])).toBe(0); +}); diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js index 29dad81f0..802cb86af 100644 --- a/Sprint-1/refactor/includes.js +++ b/Sprint-1/refactor/includes.js @@ -1,12 +1,12 @@ // Refactor the implementation of includes to use a for...of loop function includes(list, target) { - for (let index = 0; index < list.length; index++) { - const element = list[index]; + for (const element of list) { if (element === target) { return true; } } + return false; } diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..00c16466e 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,5 @@ // Predict and explain first... - +//code won't work because it's looking for the house number in the wrong place // 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..5b6ea1e48 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,5 @@ // Predict and explain first... - +//will give an error because author is an object, and you can't use a for...of loop directly on 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) { +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..faa715f1d 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,5 @@ // Predict and explain first... - +//it won't print the ingredients properly because recipe is an object // 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? @@ -11,5 +11,4 @@ const recipe = { }; console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +Ingredients:${recipe.ingredients.join("\n")}`); diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..67bad14f5 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,9 @@ -function contains() {} +function contains(object, property) { + if (typeof object !== "object" || object === null || Array.isArray(object)) { + return false; + } + + return object.hasOwnProperty(property); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..798b9ccb8 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -8,28 +8,35 @@ E.g. contains({a: 1, b: 2}, 'a') // returns true as the object contains a key of 'a' E.g. contains({a: 1, b: 2}, 'c') // returns false -as the object doesn't contains a key of 'c' +as the object doesn't contain a key of 'c' */ // Acceptance criteria: -// 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 - // 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 returns true when property exists", () => { + expect(contains({ a: 1, b: 2 }, "a")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains returns false when property does not exist", () => { + 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 returns false when passed an array", () => { + expect(contains([], "length")).toBe(false); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..e87f9c05d 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,14 @@ -function createLookup() { - // implementation here +function createLookup(countryCurrencyPairs) { + const lookup = {}; + + for (const pair of countryCurrencyPairs) { + const countryCode = pair[0]; + const currencyCode = pair[1]; + + 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..d5cb19e63 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -33,3 +33,14 @@ It should return: 'CA': 'CAD' } */ +test("creates a country currency code lookup for multiple codes", () => { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + + expect(createLookup(countryCurrencyPairs)).toEqual({ + US: "USD", + CA: "CAD", + }); +}); diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..1b287f1c3 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,13 +1,47 @@ function parseQueryString(queryString) { const queryParams = {}; + + if (queryString === null || queryString === undefined) { + return queryParams; + } + if (queryString.length === 0) { return queryParams; } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + // Ignore empty pairs + if (pair === "") { + continue; + } + + // Split only on the first "=" + const [keyPart, ...valueParts] = pair.split("="); + + let key = keyPart; + let value = valueParts.join("="); + + // If there is no "=" the value should be empty + if (valueParts.length === 0) { + value = ""; + } + + // Replace + with spaces and decode URL encoding + key = decodeURIComponent(key.replace(/\+/g, " ")); + value = decodeURIComponent(value.replace(/\+/g, " ")); + + // Handle duplicate keys + if (Object.prototype.hasOwnProperty.call(queryParams, key)) { + if (Array.isArray(queryParams[key])) { + queryParams[key].push(value); + } else { + queryParams[key] = [queryParams[key], value]; + } + } else { + queryParams[key] = value; + } } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 328b8df61..b468ef9d1 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({ @@ -46,3 +46,37 @@ test("should store values of a key in an array when the key has 2 or more values foo: "bar", }); }); + +test("should return an empty object for an empty query string", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("should handle values containing multiple equals signs", () => { + expect(parseQueryString("url=a=b=c")).toEqual({ + url: "a=b=c", + }); +}); + +test("should ignore multiple empty pairs", () => { + expect(parseQueryString("&&foo=bar&&")).toEqual({ + foo: "bar", + }); +}); + +test("should decode spaces using percent encoding", () => { + expect(parseQueryString("name=John%20Doe")).toEqual({ + name: "John Doe", + }); +}); + +test("should handle empty values with duplicate keys", () => { + expect(parseQueryString("key=&key=")).toEqual({ + key: ["", ""], + }); +}); + +test("should handle duplicate empty keys", () => { + expect(parseQueryString("=one&=two")).toEqual({ + "": ["one", "two"], + }); +}); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..68af13a7a 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,19 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("Input must be an array"); + } + + const counts = {}; + + for (const item of items) { + if (counts[item]) { + counts[item]++; + } else { + counts[item] = 1; + } + } + + return counts; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..b729296c3 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -14,21 +14,81 @@ 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"); +test("tally on an empty array returns an empty object", () => { + expect(tally([])).toEqual({}); +}); + +// Given an array with one item +// When passed to tally +// Then it should return the item with a count of 1 +test("tally counts a single item", () => { + expect(tally(["a"])).toEqual({ + a: 1, + }); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("tally counts duplicate items", () => { + expect(tally(["a", "a", "a"])).toEqual({ + a: 3, + }); +}); + +// Given an array with multiple unique items +// When passed to tally +// Then it should return the count for each item +test("tally counts multiple different items", () => { + expect(tally(["a", "a", "b", "c"])).toEqual({ + a: 2, + b: 1, + c: 1, + }); +}); + +// Given an array with numbers +// When passed to tally +// Then it should count numbers correctly +test("tally counts numbers", () => { + expect(tally([1, 1, 2, 3, 3])).toEqual({ + 1: 2, + 2: 1, + 3: 2, + }); +}); + +// Given an array with mixed strings +// When passed to tally +// Then it should count each string correctly +test("tally counts different strings", () => { + expect(tally(["cat", "dog", "cat", "bird"])).toEqual({ + cat: 2, + dog: 1, + bird: 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", () => { + expect(() => tally("hello")).toThrow(); +}); + +// Given null as input +// When passed to tally +// Then it should throw an error +test("tally throws an error for null input", () => { + expect(() => tally(null)).toThrow(); +}); + +// Given undefined as input +// When passed to tally +// Then it should throw an error +test("tally throws an error for undefined input", () => { + expect(() => tally(undefined)).toThrow(); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..c082c8ef3 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,20 +10,22 @@ 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 } +module.exports = invert; +// 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? - +//returns an array of key-value pairs and we need both the key and the value to swap them // d) Explain why the current return value is different from the target output - +//creates a property called "key" instead of using the actual key variable // e) Fix the implementation of invert (and write tests to prove it's fixed!) diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..ac0b9632e --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,14 @@ +const invert = require("./invert"); + +test("inverts an object", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ + 1: "a", + 2: "b", + }); +}); + +test("inverts a single key-value pair", () => { + expect(invert({ x: 10 })).toEqual({ + 10: "x", + }); +});