Skip to content

Commit 1d88e87

Browse files
feat: add interquartile range algorithm
1 parent 5c39e87 commit 1d88e87

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

‎Maths/InterquartileRange.js‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @function interquartileRange
3+
* @description Calculates the interquartile range (IQR) of an array.
4+
* IQR is the difference between the 75th and 25th percentiles,
5+
* representing the middle 50% spread of the data.
6+
* @param {number[]} arr - An array of numbers
7+
* @returns {number} - The interquartile range
8+
* @see https://en.wikipedia.org/wiki/Interquartile_range
9+
* @example interquartileRange([1, 2, 3, 4, 5]) => 2
10+
* @example interquartileRange([1, 2, 3, 4, 5, 6, 7]) => 3
11+
*/
12+
const interquartileRange = (arr) => {
13+
if (!Array.isArray(arr) || arr.length === 0) {
14+
throw new TypeError('Input must be a non-empty array')
15+
}
16+
if (!arr.every((n) => typeof n === 'number' && !isNaN(n))) {
17+
throw new TypeError('All elements must be numbers')
18+
}
19+
const sorted = [...arr].sort((a, b) => a - b)
20+
const getPercentile = (data, p) => {
21+
if (p === 0) return data[0]
22+
if (p === 100) return data[data.length - 1]
23+
const index = (p / 100) * (data.length - 1)
24+
const lower = Math.floor(index)
25+
const upper = Math.ceil(index)
26+
return data[lower] + (data[upper] - data[lower]) * (index - lower)
27+
}
28+
return getPercentile(sorted, 75) - getPercentile(sorted, 25)
29+
}
30+
31+
export { interquartileRange }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { interquartileRange } from '../InterquartileRange.js'
2+
3+
describe('interquartileRange', () => {
4+
test('interquartileRange([1,2,3,4,5]) returns 2', () => {
5+
expect(interquartileRange([1, 2, 3, 4, 5])).toBeCloseTo(2)
6+
})
7+
8+
test('interquartileRange([1,2,3,4,5,6,7]) returns 3', () => {
9+
expect(interquartileRange([1, 2, 3, 4, 5, 6, 7])).toBeCloseTo(3)
10+
})
11+
12+
test('interquartileRange identical elements returns 0', () => {
13+
expect(interquartileRange([5, 5, 5, 5])).toBe(0)
14+
})
15+
16+
test('interquartileRange works with unsorted array', () => {
17+
expect(interquartileRange([5, 3, 1, 4, 2])).toBeCloseTo(2)
18+
})
19+
20+
test('interquartileRange works with negative numbers', () => {
21+
expect(interquartileRange([-3, -2, -1, 0, 1, 2, 3])).toBeCloseTo(3)
22+
})
23+
24+
test('interquartileRange does not mutate original array', () => {
25+
const arr = [5, 3, 1, 4, 2]
26+
interquartileRange(arr)
27+
expect(arr).toEqual([5, 3, 1, 4, 2])
28+
})
29+
30+
test('throws TypeError for empty array', () => {
31+
expect(() => interquartileRange([])).toThrow(TypeError)
32+
})
33+
34+
test('throws TypeError for non-array input', () => {
35+
expect(() => interquartileRange('123')).toThrow(TypeError)
36+
})
37+
38+
test('throws TypeError for array with non-numbers', () => {
39+
expect(() => interquartileRange([1, 'two', 3])).toThrow(TypeError)
40+
})
41+
})

0 commit comments

Comments
 (0)