diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index b22590bc6..5c5b796e1 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -2,8 +2,8 @@ // Start by running the tests for this function // If you're in the Sprint-1 directory, you can run `npm test -- fix` to run the tests in the fix directory -// Hint: Please consider scenarios when 'list' doesn't have numbers (the function is expected to return null) -// or 'list' has mixed values (the function is expected to sort only numbers). +// Hint: Please consider scenarios when 'list' isn't an array, is empty, +// or contains values that aren't numbers (the function is expected to throw - see the tests). function calculateMedian(list) { const middleIndex = Math.floor(list.length / 2); diff --git a/Sprint-1/fix/median.test.js b/Sprint-1/fix/median.test.js index 21da654d7..51d61cc86 100644 --- a/Sprint-1/fix/median.test.js +++ b/Sprint-1/fix/median.test.js @@ -7,25 +7,45 @@ const calculateMedian = require("./median.js"); describe("calculateMedian", () => { - [ - { 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)) - ); - - [ - { 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 }, - { input: [110, 20, 0], expected: 20 }, - { input: [6, -2, 2, 12, 14], expected: 6 }, - ].forEach(({ input, expected }) => - it(`returns the correct median for unsorted array [${input}]`, () => expect(calculateMedian(input)).toEqual(expected)) - ); + it("returns the median for [1, 2, 3]", () => { + expect(calculateMedian([1, 2, 3])).toEqual(2); + }); + + it("returns the median for [1, 2, 3, 4, 5]", () => { + expect(calculateMedian([1, 2, 3, 4, 5])).toEqual(3); + }); + + it("returns the median for [1, 2, 3, 4]", () => { + expect(calculateMedian([1, 2, 3, 4])).toEqual(2.5); + }); + + it("returns the median for [1, 2, 3, 4, 5, 6]", () => { + expect(calculateMedian([1, 2, 3, 4, 5, 6])).toEqual(3.5); + }); + + it("returns the correct median for unsorted array [3, 1, 2]", () => { + expect(calculateMedian([3, 1, 2])).toEqual(2); + }); + + it("returns the correct median for unsorted array [5, 1, 3, 4, 2]", () => { + expect(calculateMedian([5, 1, 3, 4, 2])).toEqual(3); + }); + + it("returns the correct median for unsorted array [4, 2, 1, 3]", () => { + expect(calculateMedian([4, 2, 1, 3])).toEqual(2.5); + }); + + it("returns the correct median for unsorted array [6, 1, 5, 3, 2, 4]", () => { + expect(calculateMedian([6, 1, 5, 3, 2, 4])).toEqual(3.5); + }); + + it("returns the correct median for unsorted array [110, 20, 0]", () => { + expect(calculateMedian([110, 20, 0])).toEqual(20); + }); + + it("returns the correct median for unsorted array [6, -2, 2, 12, 14]", () => { + expect(calculateMedian([6, -2, 2, 12, 14])).toEqual(6); + }); it("doesn't modify the input array [3, 1, 2]", () => { const list = [3, 1, 2]; @@ -33,18 +53,66 @@ describe("calculateMedian", () => { expect(list).toEqual([3, 1, 2]); }); - [ '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)) - ); + // There is no median of an empty array, so calculateMedian should throw + it("throws when given an empty array", () => { + expect(() => calculateMedian([])).toThrow( + new Error("calculateMedian requires a non-empty array") + ); + }); + + // Input that isn't an array should throw + it("throws when given a string", () => { + expect(() => calculateMedian("banana")).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + it("throws when given a number", () => { + expect(() => calculateMedian(123)).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + it("throws when given null", () => { + expect(() => calculateMedian(null)).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + it("throws when given an object", () => { + expect(() => calculateMedian({})).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + it("throws when called with no argument", () => { + expect(() => calculateMedian()).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + // Arrays containing any non-number value should throw, rather than filtering them out + it("throws for an array of strings", () => { + expect(() => calculateMedian(["ten", "twenty", "thirty"])).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + it("throws for an array mixing numbers and strings", () => { + expect(() => calculateMedian([1, "2", 3, "4", 5])).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + it("throws for an array containing null", () => { + expect(() => calculateMedian([1, 2, null, 4])).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); + + it("throws for an array containing undefined", () => { + expect(() => calculateMedian([3, 1, undefined, 2])).toThrow( + new Error("calculateMedian requires an array of numbers") + ); + }); }); diff --git a/Sprint-1/implement/describe-median.js b/Sprint-1/implement/describe-median.js new file mode 100644 index 000000000..3381cfc0a --- /dev/null +++ b/Sprint-1/implement/describe-median.js @@ -0,0 +1,25 @@ +// Don't change this function. +function calculateMedian(list) { + if (!Array.isArray(list)) { + throw new Error("calculateMedian requires an array of numbers"); + } + if (list.length === 0) { + throw new Error("calculateMedian requires a non-empty array"); + } + for (const item of list) { + if (typeof item !== "number") { + throw new Error("calculateMedian requires an array of numbers"); + } + } + const middleIndex = Math.floor(list.length / 2); + const sorted = [...list].sort((a, b) => a - b); + if (sorted.length % 2 === 0) { + return (sorted[middleIndex - 1] + sorted[middleIndex]) / 2; + } + return sorted[middleIndex]; +} + +// Implement this function. See describe-median.test.js for the acceptance criteria. +function describeMedian(list) {} + +module.exports = describeMedian; diff --git a/Sprint-1/implement/describe-median.test.js b/Sprint-1/implement/describe-median.test.js new file mode 100644 index 000000000..e7d18a264 --- /dev/null +++ b/Sprint-1/implement/describe-median.test.js @@ -0,0 +1,32 @@ +/* Describe the median, or explain why there isn't one + +calculateMedian is given to you at the top of describe-median.js. It throws +when it can't calculate a median. Don't change it. + +Implement describeMedian, which calls calculateMedian and returns a sentence +instead of crashing. + +E.g. describeMedian([1, 2, 3]), target output: "The median is 2" +E.g. describeMedian([]), target output: "Could not calculate a median: calculateMedian requires a non-empty array" + +You'll need a try/catch block for this. The catch block must use the error's +message, so the caller can see what went wrong. +*/ + +const describeMedian = require("./describe-median.js"); + +// Acceptance criteria: + +// Given an array of numbers +// When passed to describeMedian +// Then it should return "The median is " followed by the median +// Delete this test.todo and replace it with a test. +test.todo('given [1, 2, 3], returns "The median is 2"'); + +// Given an empty array +// When passed to describeMedian +// Then it should return "Could not calculate a median: calculateMedian requires a non-empty array" + +// Given something that isn't an array of numbers, e.g. "banana" +// When passed to describeMedian +// Then it should return "Could not calculate a median: calculateMedian requires an array of numbers" diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index 82f18fd88..ff3fbb5e5 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -3,7 +3,7 @@ In this kata, you will need to implement a function that find the largest numerical element of an array. E.g. max([30, 50, 10, 40]), target output: 50 -E.g. max(['hey', 10, 'hi', 60, 10]), target output: 60 (sum ignores any non-numerical elements) +E.g. max(['hey', 10, 'hi', 60, 10]) throws Error("findMax requires an array of numbers") (max can't compare non-numerical elements, so it shouldn't guess) You should implement this function in max.js, and add tests for it in this file. @@ -34,10 +34,10 @@ test.todo("given an empty array, returns -Infinity"); // When passed to the max function // Then it should return the largest decimal number -// Given an array with non-number values +// Given an array containing a value that isn't a number // When passed to the max function -// Then it should return the max and ignore non-numeric values +// Then it should throw Error("findMax requires an array of numbers") -// Given an array with only non-number values +// Given something that isn't an array at all, such as "hey", 42 or no argument // When passed to the max function -// Then it should return the least surprising value given how it behaves for all other inputs +// Then it should throw Error("findMax requires an array of numbers") diff --git a/Sprint-1/implement/mean.js b/Sprint-1/implement/mean.js new file mode 100644 index 000000000..36909ea1f --- /dev/null +++ b/Sprint-1/implement/mean.js @@ -0,0 +1,3 @@ +function calculateMean(list) {} + +module.exports = calculateMean; diff --git a/Sprint-1/implement/mean.test.js b/Sprint-1/implement/mean.test.js new file mode 100644 index 000000000..847b88676 --- /dev/null +++ b/Sprint-1/implement/mean.test.js @@ -0,0 +1,50 @@ +/* Calculate the mean of an array of numbers + +In this kata, you will need to implement a function that calculates the mean of an array of numbers. + +E.g. calculateMean([1, 2, 6]), target output: 3 +E.g. calculateMean([]) throws Error("calculateMean requires a non-empty array") +E.g. calculateMean("banana") throws Error("calculateMean requires an array of numbers") + +There is no mean of an empty array, and a string isn't an array of numbers, so +calculateMean should throw rather than return a value. This is the same problem +calculateMedian solves in the prep and in the fix directory. + +You should implement this function in mean.js, and add tests for it in this file. + +We have set things up already so that this file can see your function from the other file. +*/ + +const calculateMean = require("./mean.js"); + +// Acceptance criteria: + +// Given an array of numbers +// When passed to calculateMean +// Then it should return their mean +// Delete this test.todo and replace it with a test. +test.todo("given [1, 2, 6], returns 3"); + +// Given an array with a single number +// When passed to calculateMean +// Then it should return that number + +// Given an array containing negative or decimal numbers +// When passed to calculateMean +// Then it should return the correct mean + +// Given an empty array +// When passed to calculateMean +// Then it should throw Error("calculateMean requires a non-empty array") + +// Given a value that isn't an array, e.g. "banana", 42, null or {} +// When passed to calculateMean +// Then it should throw Error("calculateMean requires an array of numbers") + +// Given no argument at all +// When passed to calculateMean +// Then it should throw Error("calculateMean requires an array of numbers") + +// Given an array containing a non-number value, e.g. [1, "2", 3] +// When passed to calculateMean +// Then it should throw Error("calculateMean requires an array of numbers") diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index dd0a090ca..a288e0f0a 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -3,7 +3,7 @@ In this kata, you will need to implement a function that sums the numerical elements of an array E.g. sum([10, 20, 30]), target output: 60 -E.g. sum(['hey', 10, 'hi', 60, 10]), target output: 80 (ignore any non-numerical elements) +E.g. sum(['hey', 10, 'hi', 60, 10]) throws Error("sum requires an array of numbers") (sum can't add non-numerical elements, so it shouldn't guess) */ const sum = require("./sum.js"); @@ -27,10 +27,10 @@ test.todo("given an empty array, returns 0") // When passed to the sum function // Then it should return the correct total sum -// Given an array containing non-number values +// Given an array containing a value that isn't a number // When passed to the sum function -// Then it should ignore the non-numerical values and return the sum of the numerical elements +// Then it should throw Error("sum requires an array of numbers") -// Given an array with only non-number values +// Given something that isn't an array at all, such as "hey", 42 or no argument // When passed to the sum function -// Then it should return the least surprising value given how it behaves for all other inputs +// Then it should throw Error("sum requires an array of numbers") diff --git a/Sprint-1/readme.md b/Sprint-1/readme.md index cf282a64b..8a35c3405 100644 --- a/Sprint-1/readme.md +++ b/Sprint-1/readme.md @@ -31,6 +31,8 @@ Here is a recommended order: 1. `max.test.js` 2. `sum.test.js` 3. `dedupe.test.js` +4. `mean.test.js` +5. `describe-median.test.js` Again, you can run just the test for the `implement` directory if you're in the `Sprint-1` directory by running `npm test -- implement`.