diff --git a/Maths/HarmonicMean.js b/Maths/HarmonicMean.js new file mode 100644 index 0000000000..d618bb9fb2 --- /dev/null +++ b/Maths/HarmonicMean.js @@ -0,0 +1,24 @@ +/** + * @function harmonicMean + * @description Calculates the harmonic mean of an array of positive numbers. + * The harmonic mean is the reciprocal of the arithmetic mean of reciprocals. + * Formula: n / (1/x1 + 1/x2 + ... + 1/xn) + * @param {number[]} arr - An array of positive numbers + * @returns {number} - The harmonic mean + * @see https://en.wikipedia.org/wiki/Harmonic_mean + * @example harmonicMean([1, 2, 4]) => 1.714 + * @example harmonicMean([2, 2, 2]) => 2 + * @example harmonicMean([1, 4]) => 1.6 + */ +const harmonicMean = (arr) => { + if (!Array.isArray(arr) || arr.length === 0) { + throw new TypeError('Input must be a non-empty array') + } + if (!arr.every((n) => typeof n === 'number' && !isNaN(n) && n > 0)) { + throw new TypeError('All elements must be positive numbers') + } + const sumOfReciprocals = arr.reduce((sum, n) => sum + 1 / n, 0) + return arr.length / sumOfReciprocals +} + +export { harmonicMean } diff --git a/Maths/test/HarmonicMean.test.js b/Maths/test/HarmonicMean.test.js new file mode 100644 index 0000000000..9882c12771 --- /dev/null +++ b/Maths/test/HarmonicMean.test.js @@ -0,0 +1,49 @@ +import { harmonicMean } from '../HarmonicMean.js' + +describe('harmonicMean', () => { + test('harmonicMean([2, 2, 2]) returns 2', () => { + expect(harmonicMean([2, 2, 2])).toBeCloseTo(2) + }) + + test('harmonicMean([1, 4]) returns 1.6', () => { + expect(harmonicMean([1, 4])).toBeCloseTo(1.6) + }) + + test('harmonicMean([1, 2, 4]) returns ~1.714', () => { + expect(harmonicMean([1, 2, 4])).toBeCloseTo(1.714, 2) + }) + + test('harmonicMean single element returns that element', () => { + expect(harmonicMean([5])).toBeCloseTo(5) + }) + + test('harmonicMean([1, 1, 1]) returns 1', () => { + expect(harmonicMean([1, 1, 1])).toBeCloseTo(1) + }) + + test('harmonicMean([2, 4, 8]) returns ~3.428', () => { + expect(harmonicMean([2, 4, 8])).toBeCloseTo(3.428, 2) + }) + + test('harmonicMean does not mutate original array', () => { + const arr = [1, 2, 4] + harmonicMean(arr) + expect(arr).toEqual([1, 2, 4]) + }) + + test('throws TypeError for empty array', () => { + expect(() => harmonicMean([])).toThrow(TypeError) + }) + + test('throws TypeError for non-array input', () => { + expect(() => harmonicMean('123')).toThrow(TypeError) + }) + + test('throws TypeError for zero values', () => { + expect(() => harmonicMean([1, 0, 3])).toThrow(TypeError) + }) + + test('throws TypeError for negative values', () => { + expect(() => harmonicMean([1, -2, 3])).toThrow(TypeError) + }) +})