Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions Maths/WeightedAverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @function weightedAverage
* @description Calculates the weighted average of an array of numbers
* given their corresponding weights. Each value is multiplied by its
* weight and the sum is divided by the total weight.
* @param {number[]} values - An array of numbers
* @param {number[]} weights - An array of corresponding weights
* @returns {number} - The weighted average
* @see https://en.wikipedia.org/wiki/Weighted_arithmetic_mean
* @example weightedAverage([1, 2, 3], [1, 1, 1]) => 2
* @example weightedAverage([1, 2, 3], [1, 2, 3]) => 2.333
*/
const weightedAverage = (values, weights) => {
if (!Array.isArray(values) || !Array.isArray(weights)) {
throw new TypeError('Both inputs must be arrays')
}
if (values.length === 0 || weights.length === 0) {
throw new TypeError('Arrays must not be empty')
}
if (values.length !== weights.length) {
throw new TypeError('Values and weights must have the same length')
}
if (!values.every((n) => typeof n === 'number' && !isNaN(n))) {
throw new TypeError('All values must be numbers')
}
if (!weights.every((n) => typeof n === 'number' && !isNaN(n) && n >= 0)) {
throw new TypeError('All weights must be non-negative numbers')
}
const totalWeight = weights.reduce((sum, w) => sum + w, 0)
if (totalWeight === 0) {
throw new RangeError('Total weight must not be zero')
}
const weightedSum = values.reduce((sum, v, i) => sum + v * weights[i], 0)
return weightedSum / totalWeight
}

export { weightedAverage }
47 changes: 47 additions & 0 deletions Maths/test/WeightedAverage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { weightedAverage } from '../WeightedAverage.js'

describe('weightedAverage', () => {
test('equal weights gives arithmetic mean', () => {
expect(weightedAverage([1, 2, 3], [1, 1, 1])).toBeCloseTo(2)
})

test('weightedAverage([1,2,3],[1,2,3]) returns ~2.333', () => {
expect(weightedAverage([1, 2, 3], [1, 2, 3])).toBeCloseTo(2.333, 2)
})

test('weightedAverage single element returns that element', () => {
expect(weightedAverage([5], [1])).toBeCloseTo(5)
})

test('weightedAverage with zero weight ignores that element', () => {
expect(weightedAverage([1, 2, 3], [0, 1, 0])).toBeCloseTo(2)
})

test('weightedAverage([10, 20], [1, 3]) returns 17.5', () => {
expect(weightedAverage([10, 20], [1, 3])).toBeCloseTo(17.5)
})

test('weightedAverage([1, 2], [3, 1]) returns 1.25', () => {
expect(weightedAverage([1, 2], [3, 1])).toBeCloseTo(1.25)
})

test('throws TypeError for non-array input', () => {
expect(() => weightedAverage('123', [1, 2])).toThrow(TypeError)
})

test('throws TypeError for empty arrays', () => {
expect(() => weightedAverage([], [])).toThrow(TypeError)
})

test('throws TypeError for different length arrays', () => {
expect(() => weightedAverage([1, 2], [1])).toThrow(TypeError)
})

test('throws TypeError for negative weights', () => {
expect(() => weightedAverage([1, 2], [-1, 1])).toThrow(TypeError)
})

test('throws RangeError for all zero weights', () => {
expect(() => weightedAverage([1, 2], [0, 0])).toThrow(RangeError)
})
})
Loading