Skip to content

Commit 6ac1606

Browse files
feat: add weighted average algorithm
1 parent 5c39e87 commit 6ac1606

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

‎Maths/WeightedAverage.js‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @function weightedAverage
3+
* @description Calculates the weighted average of an array of numbers
4+
* given their corresponding weights. Each value is multiplied by its
5+
* weight and the sum is divided by the total weight.
6+
* @param {number[]} values - An array of numbers
7+
* @param {number[]} weights - An array of corresponding weights
8+
* @returns {number} - The weighted average
9+
* @see https://en.wikipedia.org/wiki/Weighted_arithmetic_mean
10+
* @example weightedAverage([1, 2, 3], [1, 1, 1]) => 2
11+
* @example weightedAverage([1, 2, 3], [1, 2, 3]) => 2.333
12+
*/
13+
const weightedAverage = (values, weights) => {
14+
if (!Array.isArray(values) || !Array.isArray(weights)) {
15+
throw new TypeError('Both inputs must be arrays')
16+
}
17+
if (values.length === 0 || weights.length === 0) {
18+
throw new TypeError('Arrays must not be empty')
19+
}
20+
if (values.length !== weights.length) {
21+
throw new TypeError('Values and weights must have the same length')
22+
}
23+
if (!values.every((n) => typeof n === 'number' && !isNaN(n))) {
24+
throw new TypeError('All values must be numbers')
25+
}
26+
if (!weights.every((n) => typeof n === 'number' && !isNaN(n) && n >= 0)) {
27+
throw new TypeError('All weights must be non-negative numbers')
28+
}
29+
const totalWeight = weights.reduce((sum, w) => sum + w, 0)
30+
if (totalWeight === 0) {
31+
throw new RangeError('Total weight must not be zero')
32+
}
33+
const weightedSum = values.reduce((sum, v, i) => sum + v * weights[i], 0)
34+
return weightedSum / totalWeight
35+
}
36+
37+
export { weightedAverage }

‎Maths/test/WeightedAverage.test.js‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { weightedAverage } from '../WeightedAverage.js'
2+
3+
describe('weightedAverage', () => {
4+
test('equal weights gives arithmetic mean', () => {
5+
expect(weightedAverage([1, 2, 3], [1, 1, 1])).toBeCloseTo(2)
6+
})
7+
8+
test('weightedAverage([1,2,3],[1,2,3]) returns ~2.333', () => {
9+
expect(weightedAverage([1, 2, 3], [1, 2, 3])).toBeCloseTo(2.333, 2)
10+
})
11+
12+
test('weightedAverage single element returns that element', () => {
13+
expect(weightedAverage([5], [1])).toBeCloseTo(5)
14+
})
15+
16+
test('weightedAverage with zero weight ignores that element', () => {
17+
expect(weightedAverage([1, 2, 3], [0, 1, 0])).toBeCloseTo(2)
18+
})
19+
20+
test('weightedAverage([10, 20], [1, 3]) returns 17.5', () => {
21+
expect(weightedAverage([10, 20], [1, 3])).toBeCloseTo(17.5)
22+
})
23+
24+
test('weightedAverage([1, 2], [3, 1]) returns 1.25', () => {
25+
expect(weightedAverage([1, 2], [3, 1])).toBeCloseTo(1.25)
26+
})
27+
28+
test('throws TypeError for non-array input', () => {
29+
expect(() => weightedAverage('123', [1, 2])).toThrow(TypeError)
30+
})
31+
32+
test('throws TypeError for empty arrays', () => {
33+
expect(() => weightedAverage([], [])).toThrow(TypeError)
34+
})
35+
36+
test('throws TypeError for different length arrays', () => {
37+
expect(() => weightedAverage([1, 2], [1])).toThrow(TypeError)
38+
})
39+
40+
test('throws TypeError for negative weights', () => {
41+
expect(() => weightedAverage([1, 2], [-1, 1])).toThrow(TypeError)
42+
})
43+
44+
test('throws RangeError for all zero weights', () => {
45+
expect(() => weightedAverage([1, 2], [0, 0])).toThrow(RangeError)
46+
})
47+
})

0 commit comments

Comments
 (0)