diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index 5c5b796e1..53b578809 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -6,9 +6,28 @@ // 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)) { + 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 sorted = list.slice().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..47c5ef797 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1 +1,5 @@ -function dedupe() {} +function dedupe(arr) { + return arr.filter((item, index) => arr.indexOf(item) === index); +} + +module.exports = dedupe; diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index d7c8e3d8e..eddd4e9d4 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -16,13 +16,37 @@ 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, should return 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, should return a copy of the original array", () => { + const original = [5, 1, 2, 3, 8]; + const result = dedupe(original); + + expect(result).toEqual([5, 1, 2, 3, 8]); + expect(result).not.toBe(original); +}); // 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("Given an array of strings, removes duplicates and keeps first occurrence", () => { + expect(dedupe(["car", "plane", "car", "train", "plane"])).toEqual([ + "car", + "plane", + "train", + ]); +}); + +test("Given an array of numbers, removes duplicates and keeps first occurrence", () => { + expect(dedupe([5, 1, 1, 2, 3, 2, 5, 8])).toEqual([5, 1, 2, 3, 8]); +}); + +test("Given [1, 2, 1], returns [1, 2]", () => { + expect(dedupe([1, 2, 1])).toEqual([1, 2]); +}); diff --git a/Sprint-1/implement/describe-median.js b/Sprint-1/implement/describe-median.js index 3381cfc0a..f6fdd5660 100644 --- a/Sprint-1/implement/describe-median.js +++ b/Sprint-1/implement/describe-median.js @@ -20,6 +20,13 @@ function calculateMedian(list) { } // Implement this function. See describe-median.test.js for the acceptance criteria. -function describeMedian(list) {} +function describeMedian(list) { + try { + const median = calculateMedian(list); + return `The median is ${median}`; + } 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..f04bc5929 100644 --- a/Sprint-1/implement/describe-median.test.js +++ b/Sprint-1/implement/describe-median.test.js @@ -20,13 +20,31 @@ const describeMedian = require("./describe-median.js"); // 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"'); +test('Given an array of numbers, returns "The median is " followed by the median', () => { + expect(describeMedian([5, 6, 7])).toEqual("The median is 6"); +}); // Given an empty array // When passed to describeMedian // Then it should return "Could not calculate a median: calculateMedian requires a non-empty array" +test("Given an empty array, returns could-not-calculate message", () => { + expect(describeMedian([])).toEqual( + "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" +test("Given non-numbers, returns could-not-calculate message", () => { + expect(describeMedian(["banana"])).toEqual( + "Could not calculate a median: calculateMedian requires an array of numbers" + ); +}); + +test("Given a string, returns could-not-calculate message", () => { + expect(describeMedian("banana")).toEqual( + "Could not calculate a median: calculateMedian requires an array of numbers" + ); +}); + diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 6dd76378e..ddf6b202d 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,4 +1,26 @@ + function findMax(elements) { + if (!Array.isArray(elements)) { + throw new Error("findMax requires an array of numbers"); + } + + for (const item of elements) { + if (typeof item !== "number" || Number.isNaN(item)) { + throw new Error("findMax requires an array of numbers"); + } + } + + if (elements.length === 0) { + return -Infinity; + } + + let max = elements[0]; + 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..0485062c7 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -15,29 +15,69 @@ 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([])).toEqual(-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, should return thant number", () => { + expect(findMax([10])).toEqual(10); +}); // Given an array with both positive and negative numbers // When passed to the max function // Then it should return the largest number overall +test("Given an array with both positive and negative numbers, should return the largest number overall", () => { + expect(findMax([30, -7, 1, 45, -70, 10, -10])).toEqual(45); +}); // Given an array with just negative numbers // When passed to the max function // Then it should return the closest one to zero +test("Given an array with just negative numbers, should return the closest one to zero", () => { + expect(findMax([-15, -10, -7, -20, -70])).toEqual(-7); +}); // Given an array with decimal numbers // When passed to the max function // Then it should return the largest decimal number +test("Given an array with decimal numbers, should return the largest decimal number", () => { + expect(findMax([3.5, 2.7, 1.9, 5.1])).toEqual(5.1); +}); // Given an array containing a value that isn't a number // When passed to the max function // Then it should throw Error("findMax requires an array of numbers") - +test("Throws an error when array contains a value that isn't a number", () => { + expect(() => findMax(["plane", 10, "car", 60, 10])).toThrow( + new Error("findMax requires an array of numbers") + ); +}); // 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 throw Error("findMax requires an array of numbers") +test("Throws an error when the input is not an array at all", () => { + expect(() => findMax("hey")).toThrow( + new Error("findMax requires an array of numbers") + ); +}); + +test("Throws an error when given a number", () => { + expect(() => findMax(36)).toThrow( + new Error("findMax requires an array of numbers") + ); +}); + +test("Throws an error when called with no argument", () => { + expect(() => findMax()).toThrow( + new Error("findMax requires an array of numbers") + ); +}); + +test("Throws an error when array contains NaN", () => { + expect(() => findMax([1, NaN, 3])).toThrow( + new Error("findMax requires an array of numbers") + ); +}); diff --git a/Sprint-1/implement/mean.js b/Sprint-1/implement/mean.js index 36909ea1f..a048fdf08 100644 --- a/Sprint-1/implement/mean.js +++ b/Sprint-1/implement/mean.js @@ -1,3 +1,24 @@ -function calculateMean(list) {} +function calculateMean(list) { + if (!Array.isArray(list)) { + throw new Error("calculateMean requires an array of numbers"); + } + + if (list.length === 0) { + throw new Error("calculateMean requires a non-empty array"); + } + + for (const item of list) { + if (typeof item !== "number" || !Number.isFinite(item)) { + throw new Error("calculateMean requires an array of numbers"); + } + } + + let sum = 0; + for (const num of list) { + sum += num; + } + return sum / list.length; +} module.exports = calculateMean; + diff --git a/Sprint-1/implement/mean.test.js b/Sprint-1/implement/mean.test.js index 847b88676..388c0fa73 100644 --- a/Sprint-1/implement/mean.test.js +++ b/Sprint-1/implement/mean.test.js @@ -22,29 +22,77 @@ const calculateMean = require("./mean.js"); // 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"); + +test("Given an array of numbers, should return their mean", () => { + expect(calculateMean([3, 7, 50,40])).toEqual(25); +}); // Given an array with a single number // When passed to calculateMean // Then it should return that number +test("Given an array with a single number, should return that number", () => { + expect(calculateMean([10])).toEqual(10); +}); // Given an array containing negative or decimal numbers // When passed to calculateMean // Then it should return the correct mean +test("Given an array containing negative numbers, should return the correct mean", () => { + expect(calculateMean([-3, -50, -7, -40])).toEqual(-25); +}); + +test("Given an array containing decimal numbers, should return the correct mean", () => { + expect(calculateMean([2.2, 1.9, 3.5, 4.1])).toBeCloseTo(2.925); +}); + +test("Given an array containing both negative and decimal numbers, should return the correct mean", () => { + expect(calculateMean([2.2, -1.9, 3.5, -4.1])).toBeCloseTo(-0.075); +}); // Given an empty array // When passed to calculateMean // Then it should throw Error("calculateMean requires a non-empty array") +test("Throws an error when given an empty array", () => { + expect(() => calculateMean([])).toThrow( + new 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") +test("Throws an error when given a value that isn't an array", () => { + expect(() => calculateMean(["banana"])).toThrow( + new 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") +test("Throws an error when given no argument at all", () => { + expect(() => calculateMean()).toThrow( + new 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") +test("Throws an error when given an array containing a non-number value", () => { + expect(() => calculateMean([1, "2", 3])).toThrow( + new Error("calculateMean requires an array of numbers") + ); +}); + +test("Throws an error when array contains Infinity or -Infinity", () => { + expect(() => calculateMean([Infinity, 0, -Infinity])).toThrow( + new Error("calculateMean requires an array of numbers") + ); +}); + +test("Throws an error when array contains NaN", () => { + expect(() => calculateMean([0, NaN])).toThrow( + new Error("calculateMean requires an array of numbers") + ); +}); diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 9062aafe3..a1474f1f0 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,4 +1,19 @@ function sum(elements) { + if (!Array.isArray(elements)) { + throw new Error("sum requires an array of numbers"); + } + + for (const item of elements) { + if (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..ef591a15f 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -13,24 +13,46 @@ 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, should return 0", () => { + expect(sum([])).toEqual(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 just one number, should return that number", () => { + expect(sum([10])).toEqual(10); +}); // Given an array containing negative numbers // When passed to the sum function // Then it should still return the correct total sum +test("Given an array containing negative numbers, should still return the correct total sum", () => { + expect(sum([-3, -7, -2])).toEqual(-12); +}); // Given an array with decimal/float numbers // When passed to the sum function // Then it should return the correct total sum +test("Given an array with decimal/float numbers, should return the correct total sum", () => { + expect(sum([1.3, 2.7, 3.5])).toEqual(7.5); +}); // Given an array containing a value that isn't a number // When passed to the sum function // Then it should throw Error("sum requires an array of numbers") +test("Throws an error when an array containing a value that isn't a number", () => { + expect(() => sum([15, "car", 11])).toThrow( + new Error("sum requires an array of numbers") + ); +}); // 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 throw Error("sum requires an array of numbers") +test("Throws an error when given something that isn't an array at all", () => { + expect(() => sum("string")).toThrow( + new Error("sum requires an array of numbers") + ); +}); + diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js index 29dad81f0..07a0e6877 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; } @@ -11,3 +10,4 @@ function includes(list, target) { } module.exports = includes; + diff --git a/Sprint-1/stretch/aoc-2018-day1/solution.js b/Sprint-1/stretch/aoc-2018-day1/solution.js index e69de29bb..e45f52fcd 100644 --- a/Sprint-1/stretch/aoc-2018-day1/solution.js +++ b/Sprint-1/stretch/aoc-2018-day1/solution.js @@ -0,0 +1,44 @@ + +import fs from "node:fs"; + +// Puzzle input +// trim() removes leading/trailing whitespace so split("\n") +// does not create empty lines that become 0 after Number() +const changes = fs + .readFileSync(new URL("./input.txt", import.meta.url), "utf8") + .trim() + .split("\n") + .map(Number); + +// Part One +function calculateFrequency(changes) { + let frequency = 0; + + for (const change of changes) { + frequency += change; + } + + return frequency; +} + +// Part Two +function findRepeatedFrequency(changes) { + let frequency = 0; + const visited = new Set([0]); + + while (true) { + for (let change of changes) { + frequency += change; + + if (visited.has(frequency)) { + return frequency; + } + + visited.add(frequency); + } + } +} + +// Display answers +console.log("Part One:", calculateFrequency(changes)); +console.log("Part Two:", findRepeatedFrequency(changes)); diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..4694634ff 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -12,4 +12,9 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); + + +/* BUG: address[0] is undefined because objects aren't indexed by number. + FIX: useed the property name: address.houseNumber +console.log(`My house number is ${address.houseNumber}`); diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..8bc5d91f0 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -11,6 +11,14 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.values(author)) { console.log(value); } + + +/* BUG: Objects are not iterable, so `for...of author` throws TypeError. + FIX: Used Object.values(author) to loop over the property values. +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..8c934063e 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -12,4 +12,10 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); + + + +// BUG: ${recipe} logs out the whole object as "[object Object]". +// FIX: print title/serves from their properties, and ingredients with join("\n") +// so each item is on its own line. diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..fa093df68 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,9 @@ -function contains() {} +function contains(obj, prop) { + if (obj === null || typeof obj !== "object" || Array.isArray(obj)) { + throw new Error("contains requires an object"); + } + return Object.hasOwn(obj, prop); +} module.exports = contains; + diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 719272787..bdcf0f9be 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -23,18 +23,50 @@ as an array isn't an object // 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("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("returns false when property does not exist", () => { + expect(contains({ a: 1, b: 2 }, "c")).toBe(false); +}); // Given a value that isn't an object - an array, a string, a number, // null, or no argument at all // When passed to contains // Then it should throw Error("contains requires an object") // (careful: typeof [] and typeof null are both "object") + +test("throws an error when given an array", () => { + expect(() => contains([3, 4, 5], "a")).toThrow( + new Error("contains requires an object") + ); +}); + +test("throws an error when given a null", () => { + expect(() => contains(null, "a")).toThrow( + new Error("contains requires an object") + ); +}); + +test("throws an error when given a string", () => { + expect(() => contains("hellow", "a")).toThrow( + new Error("contains requires an object") + ); +}); + +test("throws an error when given no argument", () => { + expect(() => contains()).toThrow( + new Error("contains requires an object") + ); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..6d82731be 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,10 @@ -function createLookup() { - // implementation here + +function createLookup(pairs) { + const lookup = {}; + for (const [country, currency] of pairs) { + lookup[country] = currency; + } + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..9e3b4a4f9 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,13 @@ 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", () => { + expect( + createLookup([ + ["US", "USD"], + ["CA", "CAD"], + ]) + ).toEqual({ US: "USD", CA: "CAD" }); +}); /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..b94400b90 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,16 +1,46 @@ function parseQueryString(queryString) { const queryParams = {}; + if (queryString.length === 0) { return queryParams; } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + if (pair === "") { + continue; + } + + const eqIndex = pair.indexOf("="); + let key; + let value; + + if (eqIndex === -1) { + key = pair; + value = ""; + } else { + key = pair.slice(0, eqIndex); + value = pair.slice(eqIndex + 1); + } + + key = decodeURIComponent(key.replaceAll("+", " ")); + value = decodeURIComponent(value.replaceAll("+", " ")); + + if (Object.hasOwn(queryParams, key)) { + if (Array.isArray(queryParams[key])) { + queryParams[key].push(value); + } else { + queryParams[key] = [queryParams[key], value]; + } + } else { + queryParams[key] = value; + } } return queryParams; } module.exports = parseQueryString; + + diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..09c6f37b2 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,12 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("tally requires an array"); + } + const counts = {}; + for (const item of items) { + counts[item] = (counts[item] ?? 0) + 1; + } + return counts; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index ca763c296..5f7f1bac3 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,29 @@ 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, return counts for each unique item", () => { + expect(tally(["a", "a", "c", "b", "c", "b", "c", "e"])).toEqual({ a: 2, c: 3, b: 2, e: 1 }); +}); // Given an invalid input like a string, a number, or no argument at all // When passed to tally // Then it should throw Error("tally requires an array") +test("tally on a string, thows an error", () => { + expect(() => tally("hello")).toThrow(new Error("tally requires an array")); +}); + +test("tally on a number, thows an error", () => { + expect(() => tally(10)).toThrow(new Error("tally requires an array")); +}); + +test("tally on no argument, thows an error", () => { + expect(() => tally()).toThrow(new Error("tally requires an array")); +}); + diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..0695413a2 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,24 +6,46 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} -function invert(obj) { +export 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 } +/* +a) Current return value for invert({ a: 1 }) with the OLD bug: + { key: 1 } + +b) Current return value for invert({ a: 1, b: 2 }) with the OLD bug: + { key: 2 } + (each loop overwrites the same property named "key"; last value wins) + +c) Target return value for invert({ a: 1, b: 2 }): + { "1": "a", "2": "b" } + +Object.entries returns an array of [key, value] pairs for the object. +It is needed so the loop can read both the key and the value of each property. + +d) invertedObj.key always writes to a property literally named "key". + It ignores the loop variable `key`, so keys and values are never swapped, + and earlier entries are overwritten. The result is a new object with only + that one "key" property — not the original object, and not an inverted map. + +e) Fixed by using invertedObj[value] = key (bracket notation with the value + as the new key). Tests in invert.test.js prove the fix. +*/ + + + + + + -// b) What is the current return value when invert is called with { a: 1, b: 2 } -// c) What is the target return value when invert is called with {a : 1, b: 2} -// c) What does Object.entries return? Why is it needed in this program? -// d) Explain why the current return value is different from the target output -// e) Fix the implementation of invert (and write tests to prove it's fixed!) diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..22f6a1292 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,24 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(str) { + const counts = {}; + if (str.trim() === "") { + return counts; + } + + const words = str + .toLowerCase() + .replaceAll(/[.,!?]/g, "") + .split(/\s+/) + .filter((w) => w.length > 0); + + for (const word of words) { + counts[word] = (counts[word] ?? 0) + 1; + } + + return counts; +} + +module.exports = countWords; diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 9699bf307..729923ed4 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -12,8 +12,7 @@ // The tests must still pass after your refactor. Run them again to check. -function calculateMode(list) { - // check the input is a non-empty array of numbers +function assertValidNumbers(list) { if (!Array.isArray(list)) { throw new Error("calculateMode requires an array of numbers"); } @@ -25,25 +24,32 @@ function calculateMode(list) { throw new Error("calculateMode requires an array of numbers"); } } +} - // track frequency of each value - let freqs = new Map(); - - for (let num of list) { +function trackFrequencies(list) { + const freqs = new Map(); + for (const num of list) { freqs.set(num, (freqs.get(num) || 0) + 1); } + return freqs; +} - // Find the value with the highest frequency +function findMode(freqs) { let maxFreq = 0; let mode; - for (let [num, freq] of freqs) { + for (const [num, freq] of freqs) { if (freq > maxFreq) { mode = num; maxFreq = freq; } } - return mode; } +function calculateMode(list) { + assertValidNumbers(list); + const freqs = trackFrequencies(list); + return findMode(freqs); +} + module.exports = calculateMode; diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..7a3daca48 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -1,5 +1,5 @@ // totalTill takes an object representing coins in a till - +// // Given an object of coins // When this till object is passed to totalTill // Then it should return the total amount in pounds @@ -8,24 +8,42 @@ function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + const valueInPence = Number(coin.replace("p", "")); + total += valueInPence * quantity; } return `£${total / 100}`; } +module.exports = totalTill; + const till = { "1p": 10, "5p": 6, "50p": 4, "20p": 10, }; -const totalAmount = totalTill(till); - -// a) What is the target output when totalTill is called with the till object - -// b) Why do we need to use Object.entries inside the for...of loop in this function? - -// c) What does coin * quantity evaluate to inside the for...of loop? -// d) Write a test for this function to check it works and then fix the implementation of totalTill +// a) Target output when totalTill is called with the till object above: +// "£3.4" +// Calculation: (10*1) + (6*5) + (4*50) + (10*20) = 10 + 30 + 200 + 200 = 340 pence +// 340 / 100 = 3.4 → "£3.4" + +// b) Object.entries(till) turns the object into an array of [key, value] pairs +// e.g. [["1p", 10], ["5p", 6], ...]. +// for...of needs something iterable; Object.entries lets us read both the +// coin label and its quantity in each iteration. + +// c) Before the fix: coin * quantity was "1p" * 10 (string * number) → NaN. +// After the fix: valueInPence * quantity is e.g. 1 * 10, 5 * 6, 50 * 4, 20 * 10 +// (numbers only), and those products are added into total. + +// d) Example test (till.test.js): +// +// const totalTill = require("./till.js"); +// +// test("sums coins in the till as pounds", () => { +// expect( +// totalTill({ "1p": 10, "5p": 6, "50p": 4, "20p": 10 }) +// ).toBe("£3.4"); +// });