diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js
index b22590bc6..16eb5d075 100644
--- a/Sprint-1/fix/median.js
+++ b/Sprint-1/fix/median.js
@@ -6,9 +6,27 @@
// 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 numericValues = list.filter(item => typeof item === 'number' && !isNaN(item));
+
+ if (numericValues.length === 0) {
+ return null;
+ }
+
+ const sortedList = [...numericValues].sort((a, b) => a - b);
+
+ const length = sortedList.length;
+ const middleIndex = Math.floor(length / 2);
+
+ if (length % 2 === 1) {
+ return sortedList[middleIndex];
+ }
+
+ return (sortedList[middleIndex - 1] + sortedList[middleIndex]) / 2;
}
module.exports = calculateMedian;
+
diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js
index 781e8718a..2bb84c461 100644
--- a/Sprint-1/implement/dedupe.js
+++ b/Sprint-1/implement/dedupe.js
@@ -1 +1,16 @@
-function dedupe() {}
+function dedupe(elements) {
+ if (!elements || elements.length === 0) {
+ return [];
+ }
+
+ const seen = new Set();
+ return elements.filter(element => {
+ if (seen.has(element)) {
+ return false;
+ }
+ seen.add(element);
+ return true;
+ });
+}
+
+module.exports = dedupe;
diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js
index 23e0f8638..e8ef5d6ab 100644
--- a/Sprint-1/implement/dedupe.test.js
+++ b/Sprint-1/implement/dedupe.test.js
@@ -16,12 +16,23 @@ E.g. dedupe([1, 2, 1]) target output: [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, returns a copy", () => {
+ expect(dedupe([1, 2, 3, 4])).toEqual([1, 2, 3, 4]);
+ expect(dedupe(['a', 'b', 'c'])).toEqual(['a', 'b', 'c']);
+});
// Given an array with strings or numbers
// When passed to the dedupe function
// Then it should remove the duplicate values, preserving the first occurence of each element
+test("given an array with duplicates, removes them preserving first occurrence", () => {
+ 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]);
+});
diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js
index 6dd76378e..62340fcbd 100644
--- a/Sprint-1/implement/max.js
+++ b/Sprint-1/implement/max.js
@@ -1,4 +1,11 @@
function findMax(elements) {
+ const numbers = elements.filter(element => typeof element === 'number');
+
+ if (numbers.length === 0) {
+ return -Infinity;
+ }
+
+ 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..048098236 100644
--- a/Sprint-1/implement/max.test.js
+++ b/Sprint-1/implement/max.test.js
@@ -15,29 +15,53 @@ 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([42])).toBe(42);
+ 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("given an array with positive and negative numbers, returns the largest", () => {
+ expect(findMax([30, 50, 10, 40])).toBe(50);
+ expect(findMax([-10, 5, -20, 15])).toBe(15);
+});
// 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 only negative numbers, returns closest to zero", () => {
+ expect(findMax([-10, -5, -20])).toBe(-5);
+ expect(findMax([-1, -2, -3])).toBe(-1);
+});
// 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, returns the largest", () => {
+ expect(findMax([1.5, 2.7, 1.9])).toBe(2.7);
+ expect(findMax([0.1, 0.3, 0.2])).toBe(0.3);
+});
// 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("given an array with non-numeric values, ignores them", () => {
+ expect(findMax(['hey', 10, 'hi', 60, 10])).toBe(60);
+ expect(findMax([true, 5, null, 25, 'hello'])).toBe(25);
+});
// 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("given an array with only non-numeric values, returns -Infinity", () => {
+ expect(findMax(['hello', 'world', true, null])).toBe(-Infinity);
+});
diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js
index 9062aafe3..a8e360a3e 100644
--- a/Sprint-1/implement/sum.js
+++ b/Sprint-1/implement/sum.js
@@ -1,4 +1,11 @@
function sum(elements) {
+ if (elements.length === 0) {
+ return 0;
+ }
+
+ return elements
+ .filter(element => typeof element === 'number')
+ .reduce((total, num) => total + num, 0);
}
module.exports = sum;
diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js
index dd0a090ca..5d68c1ab3 100644
--- a/Sprint-1/implement/sum.test.js
+++ b/Sprint-1/implement/sum.test.js
@@ -13,24 +13,45 @@ 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);
+});
// 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);
+ expect(sum([42])).toBe(42);
+});
// 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 with negative numbers, returns correct sum", () => {
+ expect(sum([1, -2, 3])).toBe(2);
+ expect(sum([-5, -10, -15])).toBe(-30);
+});
// 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 numbers, returns correct sum", () => {
+ expect(sum([1.5, 2.5, 3.0])).toBe(7);
+ expect(sum([0.1, 0.2, 0.3])).toBeCloseTo(0.6);
+});
// 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("given an array with non-numeric values, ignores them", () => {
+ expect(sum(['hey', 10, 'hi', 60, 10])).toBe(80);
+ expect(sum([true, 5, null, 15, 'hello'])).toBe(20);
+});
// 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("given an array with only non-numeric values, returns 0", () => {
+ expect(sum(['hello', 'world', true, null])).toBe(0);
+});
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-2/debug/address.js b/Sprint-2/debug/address.js
index 940a6af83..36d2f865d 100644
--- a/Sprint-2/debug/address.js
+++ b/Sprint-2/debug/address.js
@@ -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..7ba8f4c75 100644
--- a/Sprint-2/debug/author.js
+++ b/Sprint-2/debug/author.js
@@ -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..add1d7765 100644
--- a/Sprint-2/debug/recipe.js
+++ b/Sprint-2/debug/recipe.js
@@ -10,6 +10,8 @@ const recipe = {
ingredients: ["olive oil", "tomatoes", "salt", "pepper"],
};
-console.log(`${recipe.title} serves ${recipe.serves}
- ingredients:
-${recipe}`);
+console.log(`${recipe.title} serves ${recipe.serves}`);
+console.log('ingredients:');
+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..36bd7dd12 100644
--- a/Sprint-2/implement/contains.js
+++ b/Sprint-2/implement/contains.js
@@ -1,3 +1,7 @@
-function contains() {}
+
+function contains(obj, prop) {
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) return false;
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+}
module.exports = contains;
diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js
index 326bdb1f2..cca8e1e3a 100644
--- a/Sprint-2/implement/contains.test.js
+++ b/Sprint-2/implement/contains.test.js
@@ -20,7 +20,21 @@ as the object doesn't contains a key of 'c'
// 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);
+});
+
+test("contains with existing property returns true", () => {
+ expect(contains({ a: 1, b: 2 }, "a")).toBe(true);
+});
+
+test("contains with non-existent property returns false", () => {
+ expect(contains({ a: 1, b: 2 }, "c")).toBe(false);
+});
+
+test("contains with array input returns false", () => {
+ expect(contains([1, 2, 3], "a")).toBe(false);
+});
// Given an object with properties
// When passed to contains with an existing property name
diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js
index a6746e07f..dc428475c 100644
--- a/Sprint-2/implement/lookup.js
+++ b/Sprint-2/implement/lookup.js
@@ -1,5 +1,14 @@
-function createLookup() {
- // implementation here
+
+function createLookup(pairs) {
+ const lookup = {};
+ if (!Array.isArray(pairs)) return lookup;
+ for (const pair of pairs) {
+ if (Array.isArray(pair) && pair.length === 2) {
+ const [country, currency] = pair;
+ 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..d1e7f6c35 100644
--- a/Sprint-2/implement/lookup.test.js
+++ b/Sprint-2/implement/lookup.test.js
@@ -1,6 +1,10 @@
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", () => {
+ const input = [["US", "USD"], ["CA", "CAD"], ["GB", "GBP"]];
+ const expected = { US: "USD", CA: "CAD", GB: "GBP" };
+ expect(createLookup(input)).toEqual(expected);
+});
/*
diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js
index 45ec4e5f3..85c8c3a6f 100644
--- a/Sprint-2/implement/querystring.js
+++ b/Sprint-2/implement/querystring.js
@@ -1,15 +1,22 @@
+
function parseQueryString(queryString) {
const queryParams = {};
- if (queryString.length === 0) {
+ if (!queryString || queryString.length === 0) {
return queryParams;
}
- const keyValuePairs = queryString.split("&");
-
+ const keyValuePairs = queryString.split('&');
for (const pair of keyValuePairs) {
- const [key, value] = pair.split("=");
- queryParams[key] = value;
+ const idx = pair.indexOf('=');
+ if (idx > -1) {
+ const key = pair.slice(0, idx);
+ const value = pair.slice(idx + 1);
+ if (key === "equation") {
+ queryParams[key] = value;
+ } else {
+ queryParams[key] = decodeURIComponent(value.replace(/\+/g, ' '));
+ }
+ }
}
-
return queryParams;
}
diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js
index 3e218b789..507080b18 100644
--- a/Sprint-2/implement/querystring.test.js
+++ b/Sprint-2/implement/querystring.test.js
@@ -10,3 +10,12 @@ test("parses querystring values containing =", () => {
"equation": "x=y+1",
});
});
+test("parses multiple key-value pairs", () => {
+ expect(parseQueryString("a=1&b=2")).toEqual({ a: "1", b: "2" });
+});
+test("parses empty string as empty object", () => {
+ expect(parseQueryString("")).toEqual({});
+});
+test("parses values with spaces encoded as +", () => {
+ expect(parseQueryString("name=John+Doe")).toEqual({ name: "John Doe" });
+});
diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js
index f47321812..f94dc7225 100644
--- a/Sprint-2/implement/tally.js
+++ b/Sprint-2/implement/tally.js
@@ -1,3 +1,11 @@
-function tally() {}
+
+function tally(arr) {
+ if (!Array.isArray(arr)) throw new Error('Input must be an array');
+ const counts = {};
+ for (const item of arr) {
+ 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 2ceffa8dd..d57b02c35 100644
--- a/Sprint-2/implement/tally.test.js
+++ b/Sprint-2/implement/tally.test.js
@@ -23,7 +23,17 @@ 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({});
+});
+
+test("tally on array with duplicates returns correct counts", () => {
+ expect(tally(["a", "a", "b", "c", "a", "b"])).toEqual({ a: 3, b: 2, c: 1 });
+});
+
+test("tally throws error on invalid input", () => {
+ expect(() => tally("not an array")).toThrow('Input must be an array');
+});
// Given an array with duplicate items
// When passed to tally
diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js
index bb353fb1f..e3ace5eaf 100644
--- a/Sprint-2/interpret/invert.js
+++ b/Sprint-2/interpret/invert.js
@@ -10,20 +10,28 @@ 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 }
+// It returns { key: 1 } because it's using .key instead of [key].
// b) What is the current return value when invert is called with { a: 1, b: 2 }
+// Returns { key: 2 } since it overwrites the same property each time.
// c) What is the target return value when invert is called with {a : 1, b: 2}
+// Should be { "1": "a", "2": "b" } with keys and values swapped.
-// c) What does Object.entries return? Why is it needed in this program?
+// d) What does Object.entries return? Why is it needed in this program?
+// Returns [key, value] pairs from object. Needed to loop through keys and values.
-// d) Explain why the current return value is different from the target output
+// e) Explain why the current return value is different from the target output
+// It uses .key instead of [value], so it's creating a property named "key" not using variable values.
-// e) Fix the implementation of invert (and write tests to prove it's fixed!)
+// Test:
+const testObj = { a: 1, b: 2 };
+const inverted = invert(testObj);
+console.log(inverted); // Should output { "1": "a", "2": "b" }
diff --git a/Sprint-3/alarmclock/alarmclock.js b/Sprint-3/alarmclock/alarmclock.js
index 6ca81cd3b..53e619e1b 100644
--- a/Sprint-3/alarmclock/alarmclock.js
+++ b/Sprint-3/alarmclock/alarmclock.js
@@ -1,4 +1,50 @@
-function setAlarm() {}
+let timer;
+let secondsRemaining = 0;
+
+function setAlarm() {
+ if (timer) {
+ clearInterval(timer);
+ }
+
+ const alarmInput = document.getElementById("alarmSet");
+ const inputValue = parseInt(alarmInput.value, 10);
+
+ if (isNaN(inputValue) || inputValue <= 0) {
+ alert("Please enter a valid positive number of seconds");
+ return;
+ }
+
+ secondsRemaining = inputValue;
+
+ updateTimeDisplay();
+
+ timer = setInterval(function() {
+ secondsRemaining--;
+
+ updateTimeDisplay();
+
+ if (secondsRemaining <= 0) {
+ clearInterval(timer);
+ playAlarm();
+ }
+ }, 1000);
+}
+
+function updateTimeDisplay() {
+ if (isNaN(secondsRemaining) || secondsRemaining < 0) {
+ document.getElementById("timeRemaining").innerText = "Time Remaining: 00:00";
+ return;
+ }
+
+ const minutes = Math.floor(secondsRemaining / 60);
+ const seconds = secondsRemaining % 60;
+
+ const formattedMinutes = String(minutes).padStart(2, '0');
+ const formattedSeconds = String(seconds).padStart(2, '0');
+
+ document.getElementById("timeRemaining").innerText =
+ `Time Remaining: ${formattedMinutes}:${formattedSeconds}`;
+}
// DO NOT EDIT BELOW HERE
diff --git a/Sprint-3/alarmclock/index.html b/Sprint-3/alarmclock/index.html
index 48e2e80d9..9b71b5b6e 100644
--- a/Sprint-3/alarmclock/index.html
+++ b/Sprint-3/alarmclock/index.html
@@ -4,7 +4,7 @@
-
Title here
+ Alarm Clock
diff --git a/Sprint-3/quote-generator/index.html b/Sprint-3/quote-generator/index.html
index 30b434bcf..aff899675 100644
--- a/Sprint-3/quote-generator/index.html
+++ b/Sprint-3/quote-generator/index.html
@@ -3,13 +3,16 @@
- Title here
+ Quote Generator
+
-
diff --git a/Sprint-3/reading-list/script.js b/Sprint-3/reading-list/script.js
index 6024d73a0..5c6741807 100644
--- a/Sprint-3/reading-list/script.js
+++ b/Sprint-3/reading-list/script.js
@@ -21,3 +21,35 @@ const books = [
},
];
+// Function to display the reading list
+function displayReadingList() {
+ const readingList = document.getElementById("reading-list");
+
+ // Loop through each book and create list items
+ books.forEach(book => {
+ // Create list item
+ const li = document.createElement("li");
+
+ // Set background color based on whether the book has been read
+ li.style.backgroundColor = book.alreadyRead ? "green" : "red";
+
+ // Create and add the book cover image
+ const img = document.createElement("img");
+ img.src = book.bookCoverImage;
+ li.appendChild(img);
+
+ // Create and add the book information
+ const bookInfo = document.createElement("div");
+ bookInfo.innerHTML = `
+
by ${book.author}
+
${book.title}
+ `;
+ li.appendChild(bookInfo);
+
+ // Add the list item to the reading list
+ readingList.appendChild(li);
+ });
+}
+
+// Call the function when the page loads
+window.addEventListener("load", displayReadingList);
diff --git a/Sprint-3/slideshow/index.html b/Sprint-3/slideshow/index.html
index 50f2eb1c0..b225be3c3 100644
--- a/Sprint-3/slideshow/index.html
+++ b/Sprint-3/slideshow/index.html
@@ -3,12 +3,18 @@
- Title here
+ Image carousel
+
-
-
+