Skip to content

Commit dd51010

Browse files
Add solutions for mean, describe-median, querystring and mode
1 parent 38f06fb commit dd51010

6 files changed

Lines changed: 175 additions & 16 deletions

File tree

Sprint-1/implement/describe-median.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ function calculateMedian(list) {
2020
}
2121

2222
// Implement this function. See describe-median.test.js for the acceptance criteria.
23-
function describeMedian(list) {}
23+
24+
// Explanation:
25+
// calculateMedian throws when it can't produce a median. Rather than letting
26+
// that crash the caller, we try to calculate it and, if an error is thrown,
27+
// catch it and turn its message into a sentence.
28+
function describeMedian(list) {
29+
try {
30+
const median = calculateMedian(list);
31+
return `The median is ${median}`;
32+
} catch (error) {
33+
return `Could not calculate a median: ${error.message}`;
34+
}
35+
}
2436

2537
module.exports = describeMedian;

Sprint-1/implement/describe-median.test.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const describeMedian = require("./describe-median.js");
2121
// When passed to describeMedian
2222
// Then it should return "The median is " followed by the median
2323
// Delete this test.todo and replace it with a test.
24-
test.todo('given [1, 2, 3], returns "The median is 2"');
2524

2625
// Given an empty array
2726
// When passed to describeMedian
@@ -30,3 +29,23 @@ test.todo('given [1, 2, 3], returns "The median is 2"');
3029
// Given something that isn't an array of numbers, e.g. "banana"
3130
// When passed to describeMedian
3231
// Then it should return "Could not calculate a median: calculateMedian requires an array of numbers"
32+
33+
test('given [1, 2, 3], returns "The median is 2"', () => {
34+
expect(describeMedian([1, 2, 3])).toEqual("The median is 2");
35+
});
36+
37+
test("given an even-length array, describes the average of the middle two", () => {
38+
expect(describeMedian([1, 2, 3, 4])).toEqual("The median is 2.5");
39+
});
40+
41+
test("given an empty array, explains there is no median", () => {
42+
expect(describeMedian([])).toEqual(
43+
"Could not calculate a median: calculateMedian requires a non-empty array"
44+
);
45+
});
46+
47+
test("given something that isn't an array of numbers, explains why", () => {
48+
expect(describeMedian("banana")).toEqual(
49+
"Could not calculate a median: calculateMedian requires an array of numbers"
50+
);
51+
});

Sprint-1/implement/mean.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1-
function calculateMean(list) {}
1+
// Explanation:
2+
3+
// The mean is the sum of the numbers divided by how many there are.
4+
// We check the input first: it must be an array, it must not be empty (there is
5+
// no mean of nothing, and dividing by zero would give NaN), and every element
6+
// must be a number. Each bad input throws a named error rather than guessing.
7+
8+
function calculateMean(list) {
9+
if (!Array.isArray(list)) {
10+
throw new Error("calculateMean requires an array of numbers");
11+
}
12+
for (const item of list) {
13+
if (typeof item !== "number") {
14+
throw new Error("calculateMean requires an array of numbers");
15+
}
16+
}
17+
if (list.length === 0) {
18+
throw new Error("calculateMean requires a non-empty array");
19+
}
20+
21+
let total = 0;
22+
for (const item of list) {
23+
total += item;
24+
}
25+
return total / list.length;
26+
}
227

328
module.exports = calculateMean;

Sprint-1/implement/mean.test.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const calculateMean = require("./mean.js");
2323
// When passed to calculateMean
2424
// Then it should return their mean
2525
// Delete this test.todo and replace it with a test.
26-
test.todo("given [1, 2, 6], returns 3");
2726

2827
// Given an array with a single number
2928
// When passed to calculateMean
@@ -48,3 +47,61 @@ test.todo("given [1, 2, 6], returns 3");
4847
// Given an array containing a non-number value, e.g. [1, "2", 3]
4948
// When passed to calculateMean
5049
// Then it should throw Error("calculateMean requires an array of numbers")
50+
51+
test("given [1, 2, 6], returns 3", () => {
52+
expect(calculateMean([1, 2, 6])).toEqual(3);
53+
});
54+
55+
test("given an array with a single number, returns that number", () => {
56+
expect(calculateMean([7])).toEqual(7);
57+
});
58+
59+
test("given negative numbers, returns the correct mean", () => {
60+
expect(calculateMean([-4, 2, -1, 3])).toEqual(0);
61+
});
62+
63+
test("given decimal numbers, returns the correct mean", () => {
64+
expect(calculateMean([1.5, 2.5, 3.5])).toEqual(2.5);
65+
});
66+
67+
test("throws when given an empty array", () => {
68+
expect(() => calculateMean([])).toThrow(
69+
new Error("calculateMean requires a non-empty array")
70+
);
71+
});
72+
73+
test("throws when given a string", () => {
74+
expect(() => calculateMean("banana")).toThrow(
75+
new Error("calculateMean requires an array of numbers")
76+
);
77+
});
78+
79+
test("throws when given a number", () => {
80+
expect(() => calculateMean(42)).toThrow(
81+
new Error("calculateMean requires an array of numbers")
82+
);
83+
});
84+
85+
test("throws when given null", () => {
86+
expect(() => calculateMean(null)).toThrow(
87+
new Error("calculateMean requires an array of numbers")
88+
);
89+
});
90+
91+
test("throws when given an object", () => {
92+
expect(() => calculateMean({})).toThrow(
93+
new Error("calculateMean requires an array of numbers")
94+
);
95+
});
96+
97+
test("throws when called with no argument", () => {
98+
expect(() => calculateMean()).toThrow(
99+
new Error("calculateMean requires an array of numbers")
100+
);
101+
});
102+
103+
test("throws when the array contains a non-number value", () => {
104+
expect(() => calculateMean([1, "2", 3])).toThrow(
105+
new Error("calculateMean requires an array of numbers")
106+
);
107+
});

Sprint-2/implement/querystring.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,45 @@
1+
// Explanation:
2+
// Each "&"-separated piece is one key-value pair. Empty pieces (from "&&" or a
3+
// trailing "&") are skipped. We split on the first "=" only, so a value may
4+
// itself contain "=", and a piece with no "=" gets an empty string value.
5+
// decode() turns "+" into a space and decodes percent-encoded characters.
6+
// If the same key appears more than once, its values are collected into an
7+
// array instead of overwriting each other.
8+
9+
function decode(text) {
10+
return decodeURIComponent(text.replaceAll("+", " "));
11+
}
12+
113
function parseQueryString(queryString) {
214
const queryParams = {};
315
if (queryString.length === 0) {
416
return queryParams;
517
}
6-
const keyValuePairs = queryString.split("&");
718

19+
const keyValuePairs = queryString.split("&");
820
for (const pair of keyValuePairs) {
9-
const [key, value] = pair.split("=");
10-
queryParams[key] = value;
21+
if (pair === "") {
22+
continue;
23+
}
24+
25+
const equalsIndex = pair.indexOf("=");
26+
let key;
27+
let value;
28+
if (equalsIndex === -1) {
29+
key = decode(pair);
30+
value = "";
31+
} else {
32+
key = decode(pair.slice(0, equalsIndex));
33+
value = decode(pair.slice(equalsIndex + 1));
34+
}
35+
36+
if (!(key in queryParams)) {
37+
queryParams[key] = value;
38+
} else if (Array.isArray(queryParams[key])) {
39+
queryParams[key].push(value);
40+
} else {
41+
queryParams[key] = [queryParams[key], value];
42+
}
1143
}
1244

1345
return queryParams;

Sprint-2/stretch/mode.js

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@
1212

1313
// The tests must still pass after your refactor. Run them again to check.
1414

15-
function calculateMode(list) {
16-
// check the input is a non-empty array of numbers
15+
// Explanation:
16+
// Each stage becomes a function named after what it does. calculateMode then
17+
// reads as the three steps in order, and each helper can be understood and
18+
// tested on its own.
19+
20+
// Stage 1
21+
function checkIsNonEmptyArrayOfNumbers(list) {
1722
if (!Array.isArray(list)) {
1823
throw new Error("calculateMode requires an array of numbers");
1924
}
@@ -25,25 +30,34 @@ function calculateMode(list) {
2530
throw new Error("calculateMode requires an array of numbers");
2631
}
2732
}
33+
}
2834

29-
// track frequency of each value
30-
let freqs = new Map();
31-
32-
for (let num of list) {
35+
// Stage 2
36+
function countFrequencies(list) {
37+
const freqs = new Map();
38+
for (const num of list) {
3339
freqs.set(num, (freqs.get(num) || 0) + 1);
3440
}
41+
return freqs;
42+
}
3543

36-
// Find the value with the highest frequency
44+
// Stage 3
45+
function findMostFrequent(freqs) {
3746
let maxFreq = 0;
3847
let mode;
39-
for (let [num, freq] of freqs) {
48+
for (const [num, freq] of freqs) {
4049
if (freq > maxFreq) {
4150
mode = num;
4251
maxFreq = freq;
4352
}
4453
}
45-
4654
return mode;
4755
}
4856

57+
function calculateMode(list) {
58+
checkIsNonEmptyArrayOfNumbers(list);
59+
const freqs = countFrequencies(list);
60+
return findMostFrequent(freqs);
61+
}
62+
4963
module.exports = calculateMode;

0 commit comments

Comments
 (0)