From 326360da012e3011f473bc9728a039cc79d37176 Mon Sep 17 00:00:00 2001 From: sarthakgupta1907-cell Date: Mon, 9 Nov 2026 12:35:45 +0530 Subject: [PATCH] feat: add percentile algorithm --- Maths/Percentile.js | 32 +++++++++++++++++++++ Maths/test/Percentile.test.js | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 Maths/Percentile.js create mode 100644 Maths/test/Percentile.test.js diff --git a/Maths/Percentile.js b/Maths/Percentile.js new file mode 100644 index 0000000000..616d0ce8cc --- /dev/null +++ b/Maths/Percentile.js @@ -0,0 +1,32 @@ +/** + * @function percentile + * @description Calculates the nth percentile of an array of numbers. + * The percentile indicates the value below which a given percentage + * of observations fall. + * @param {number[]} arr - An array of numbers + * @param {number} p - The percentile to calculate (0-100) + * @returns {number} - The nth percentile value + * @see https://en.wikipedia.org/wiki/Percentile + * @example percentile([1, 2, 3, 4, 5], 50) => 3 + * @example percentile([1, 2, 3, 4, 5], 75) => 4 + */ +const percentile = (arr, p) => { + if (!Array.isArray(arr) || arr.length === 0) { + throw new TypeError('First argument must be a non-empty array') + } + if (!arr.every((n) => typeof n === 'number' && !isNaN(n))) { + throw new TypeError('All elements must be numbers') + } + if (typeof p !== 'number' || isNaN(p) || p < 0 || p > 100) { + throw new TypeError('Percentile must be a number between 0 and 100') + } + const sorted = [...arr].sort((a, b) => a - b) + if (p === 0) return sorted[0] + if (p === 100) return sorted[sorted.length - 1] + const index = (p / 100) * (sorted.length - 1) + const lower = Math.floor(index) + const upper = Math.ceil(index) + return sorted[lower] + (sorted[upper] - sorted[lower]) * (index - lower) +} + +export { percentile } diff --git a/Maths/test/Percentile.test.js b/Maths/test/Percentile.test.js new file mode 100644 index 0000000000..3dfa158ef9 --- /dev/null +++ b/Maths/test/Percentile.test.js @@ -0,0 +1,53 @@ +import { percentile } from '../Percentile.js' + +describe('percentile', () => { + test('percentile([1,2,3,4,5], 50) returns 3', () => { + expect(percentile([1, 2, 3, 4, 5], 50)).toBe(3) + }) + + test('percentile([1,2,3,4,5], 0) returns 1', () => { + expect(percentile([1, 2, 3, 4, 5], 0)).toBe(1) + }) + + test('percentile([1,2,3,4,5], 100) returns 5', () => { + expect(percentile([1, 2, 3, 4, 5], 100)).toBe(5) + }) + + test('percentile([1,2,3,4,5], 75) returns 4', () => { + expect(percentile([1, 2, 3, 4, 5], 75)).toBe(4) + }) + + test('percentile([1,2,3,4,5], 25) returns 2', () => { + expect(percentile([1, 2, 3, 4, 5], 25)).toBe(2) + }) + + test('percentile works with unsorted array', () => { + expect(percentile([5, 3, 1, 4, 2], 50)).toBe(3) + }) + + test('percentile single element returns that element', () => { + expect(percentile([5], 50)).toBe(5) + }) + + test('percentile does not mutate original array', () => { + const arr = [5, 3, 1, 4, 2] + percentile(arr, 50) + expect(arr).toEqual([5, 3, 1, 4, 2]) + }) + + test('throws TypeError for empty array', () => { + expect(() => percentile([], 50)).toThrow(TypeError) + }) + + test('throws TypeError for invalid percentile > 100', () => { + expect(() => percentile([1, 2, 3], 101)).toThrow(TypeError) + }) + + test('throws TypeError for invalid percentile < 0', () => { + expect(() => percentile([1, 2, 3], -1)).toThrow(TypeError) + }) + + test('throws TypeError for non-array input', () => { + expect(() => percentile('123', 50)).toThrow(TypeError) + }) +})