diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index 5c5b796e1..9e8e7ab43 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -6,9 +6,18 @@ // 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); - const median = list.splice(middleIndex, 1)[0]; - return median; + if (!Array.isArray(list) || list.some((item) => typeof item !== "number")) { + throw new Error("calculateMedian requires an array of numbers"); + } + if (list.length === 0) { + throw new Error("calculateMedian requires a non-empty array"); + } + const sorted = [...list].sort((a, b) => a - b); + const middleIndex = Math.floor(sorted.length / 2); + if (sorted.length % 2 === 0) { + return (sorted[middleIndex - 1] + sorted[middleIndex]) / 2; + } + return sorted[middleIndex]; } module.exports = calculateMedian; diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js index 781e8718a..7abb5fb70 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1 +1,5 @@ -function dedupe() {} +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 d7c8e3d8e..a98b76d42 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -16,7 +16,22 @@ 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([]); +}); + +test("given an array with no duplicates, returns a copy", () => { + const input = [1, 2, 3]; + const result = dedupe(input); + expect(result).toEqual([1, 2, 3]); + expect(result).not.toBe(input); +}); + +test("removes duplicates preserving first occurrences", () => { + expect(dedupe(["a", "a", "a", "b", "b", "c"])).toEqual(["a", "b", "c"]); + expect(dedupe([5, 1, 1, 2, 3, 2, 5, 8])).toEqual([5, 1, 2, 3, 8]); + expect(dedupe([1, 2, 1])).toEqual([1, 2]); +}); // Given an array with no duplicates // When passed to the dedupe function diff --git a/Sprint-1/implement/describe-median.js b/Sprint-1/implement/describe-median.js index 3381cfc0a..c9ce2385a 100644 --- a/Sprint-1/implement/describe-median.js +++ b/Sprint-1/implement/describe-median.js @@ -20,6 +20,12 @@ function calculateMedian(list) { } // Implement this function. See describe-median.test.js for the acceptance criteria. -function describeMedian(list) {} +function describeMedian(list) { + try { + return `The median is ${calculateMedian(list)}`; + } catch (error) { + return `Could not calculate a median: ${error.message}`; + } +} module.exports = describeMedian; diff --git a/Sprint-1/implement/describe-median.test.js b/Sprint-1/implement/describe-median.test.js index e7d18a264..698cc6cfd 100644 --- a/Sprint-1/implement/describe-median.test.js +++ b/Sprint-1/implement/describe-median.test.js @@ -21,7 +21,21 @@ const describeMedian = require("./describe-median.js"); // 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"'); +test('given [1, 2, 3], returns "The median is 2"', () => { + expect(describeMedian([1, 2, 3])).toBe("The median is 2"); +}); + +test("given an empty array, explains why", () => { + expect(describeMedian([])).toBe( + "Could not calculate a median: calculateMedian requires a non-empty array" + ); +}); + +test("given a non-array, explains why", () => { + expect(describeMedian("banana")).toBe( + "Could not calculate a median: calculateMedian requires an array of numbers" + ); +}); // Given an empty array // When passed to describeMedian diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 6dd76378e..74ed6e2bf 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,4 +1,15 @@ function findMax(elements) { + if ( + !Array.isArray(elements) || + elements.some((item) => typeof item !== "number") + ) { + throw new Error("findMax requires an array of numbers"); + } + let max = -Infinity; + for (const item of elements) { + if (item > max) max = item; + } + return max; } module.exports = findMax; diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index ff3fbb5e5..f13026fb5 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -16,7 +16,39 @@ const findMax = require("./max.js"); // 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); +}); + +test("given one number, returns it", () => { + expect(findMax([7])).toBe(7); +}); + +test("given positive and negative numbers, returns the largest", () => { + expect(findMax([30, -50, 10, 40])).toBe(40); +}); + +test("given only negative numbers, returns the closest to zero", () => { + expect(findMax([-10, -3, -20])).toBe(-3); +}); + +test("given decimals, returns the largest", () => { + expect(findMax([1.5, 2.25, 2.2])).toBe(2.25); +}); + +test("throws if an element isn't a number", () => { + expect(() => findMax(["hey", 10, "hi", 60, 10])).toThrow( + new Error("findMax requires an array of numbers") + ); +}); + +test("throws if not an array", () => { + for (const bad of ["hey", 42, undefined, null, {}]) { + expect(() => findMax(bad)).toThrow( + new Error("findMax requires an array of numbers") + ); + } +}); // Given an array with one number // When passed to the max function diff --git a/Sprint-1/implement/mean.js b/Sprint-1/implement/mean.js index 36909ea1f..fb3402ca4 100644 --- a/Sprint-1/implement/mean.js +++ b/Sprint-1/implement/mean.js @@ -1,3 +1,15 @@ -function calculateMean(list) {} +function calculateMean(list) { + if (!Array.isArray(list) || list.some((item) => typeof item !== "number")) { + throw new Error("calculateMean requires an array of numbers"); + } + if (list.length === 0) { + throw new Error("calculateMean requires a non-empty array"); + } + let total = 0; + for (const item of list) { + total += item; + } + return total / list.length; +} module.exports = calculateMean; diff --git a/Sprint-1/implement/mean.test.js b/Sprint-1/implement/mean.test.js index 847b88676..a5981f5b6 100644 --- a/Sprint-1/implement/mean.test.js +++ b/Sprint-1/implement/mean.test.js @@ -23,7 +23,38 @@ const calculateMean = require("./mean.js"); // 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"); +test("given [1, 2, 6], returns 3", () => { + expect(calculateMean([1, 2, 6])).toBe(3); +}); + +test("given a single number, returns it", () => { + expect(calculateMean([4])).toBe(4); +}); + +test("handles negative and decimal numbers", () => { + expect(calculateMean([-2, 2, -4, 4])).toBe(0); + expect(calculateMean([1.5, 2.5])).toBe(2); +}); + +test("throws for an empty array", () => { + expect(() => calculateMean([])).toThrow( + new Error("calculateMean requires a non-empty array") + ); +}); + +test("throws for non-arrays and no argument", () => { + for (const bad of ["banana", 42, null, {}, undefined]) { + expect(() => calculateMean(bad)).toThrow( + new Error("calculateMean requires an array of numbers") + ); + } +}); + +test("throws for arrays with non-numbers", () => { + expect(() => calculateMean([1, "2", 3])).toThrow( + new Error("calculateMean requires an array of numbers") + ); +}); // Given an array with a single number // When passed to calculateMean diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 9062aafe3..d9ea7575a 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,4 +1,15 @@ function sum(elements) { + if ( + !Array.isArray(elements) || + elements.some((item) => typeof item !== "number") + ) { + throw new Error("sum requires an array of numbers"); + } + let total = 0; + for (const item of elements) { + total += item; + } + return total; } module.exports = sum; diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index a288e0f0a..b116cea52 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -13,7 +13,33 @@ const sum = require("./sum.js"); // 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); +}); + +test("given one number, returns it", () => { + expect(sum([5])).toBe(5); +}); + +test("given negative numbers, returns the correct total", () => { + expect(sum([10, -30, 5])).toBe(-15); +}); + +test("given decimals, returns the correct total", () => { + expect(sum([0.5, 0.25, 1])).toBe(1.75); +}); + +test("throws if an element isn't a number", () => { + expect(() => sum(["hey", 10, "hi", 60, 10])).toThrow( + new Error("sum requires an array of numbers") + ); +}); + +test("throws if not an array", () => { + for (const bad of ["hey", 42, undefined, null, {}]) { + expect(() => sum(bad)).toThrow(new Error("sum requires an array of numbers")); + } +}); // Given an array with just one number // When passed to the sum function diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js index 29dad81f0..8c9ae2e66 100644 --- a/Sprint-1/refactor/includes.js +++ b/Sprint-1/refactor/includes.js @@ -1,8 +1,7 @@ // 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; } diff --git a/Sprint-1/stretch/aoc-2018-day1/solution.js b/Sprint-1/stretch/aoc-2018-day1/solution.js index e69de29bb..3435f615a 100644 --- a/Sprint-1/stretch/aoc-2018-day1/solution.js +++ b/Sprint-1/stretch/aoc-2018-day1/solution.js @@ -0,0 +1,28 @@ +const fs = require("fs"); +const path = require("path"); + +const changes = fs + .readFileSync(path.join(__dirname, "input.txt"), "utf8") + .split(/\r?\n/) + .filter((line) => line.trim() !== "") + .map(Number); + +// Part 1: resulting frequency after all changes +const finalFrequency = changes.reduce((total, change) => total + change, 0); +console.log("Part 1:", finalFrequency); + +// Part 2: first frequency reached twice +let frequency = 0; +const seen = new Set([0]); +let firstRepeat; +while (firstRepeat === undefined) { + for (const change of changes) { + frequency += change; + if (seen.has(frequency)) { + firstRepeat = frequency; + break; + } + seen.add(frequency); + } +} +console.log("Part 2:", firstRepeat);