# Reducing array to average readings

Taking the payload below, I need to output the average of each reading.

For example the readings for key 2 are 1, 4, 7. The average is (1+4+7)/3 = 4.

The data is formatted in multi-dimensional arrays, such that each sub-array contains pairs with a key and a reading value. For example:

[
[
[2, 1],
[4, 2],
[6, 3]
],
[
[2, 4],
[4, 5],
[6, 6]
],
[
[2, 7],
[4, 8],
[6, 9]
]
]


The final result should be:

[
[2, 4],
[4, 5],
[6, 6]
]


My effort so far seems to work.

const payload = [
[
[2, 1],
[4, 2],
[6, 3]
],
[
[2, 4],
[4, 5],
[6, 6]
],
[
[2, 7],
[4, 8],
[6, 9]
]
]

const result = payload.reduce((accumulator, currentValue, currentIndex, array) => {

if(currentIndex === 0){
return accumulator
}
return currentValue.reduce((a, c, i) => {
accumulator[i] += c
return accumulator
}, accumulator)

}).map((currentValue, index, array) => {
return [currentValue, currentValue / array.length]
})

• Above payload.reduce(...) mutates the first element of payload - that is probably unexpected behavior and could cause bugs further on. – le_m Nov 23 '17 at 18:43
• Thanks @le_m - It was unexpected, and did cause bugs. A lot of head scratching yesterday until I read your comment. – L G Nov 25 '17 at 0:04
• Please see What to do when someone answers. I have rolled back Rev 6 → 5. – 200_success Nov 25 '17 at 0:11

## 2 Answers

### Average calculation issue

The example data happens to be a 3x3 matrix of values, so the calculation of the average appears to be correct. However, try using a 2x3 matrix of values (i.e. remove the 3rd sub-array of values) - it should become clear that the divisor used in the calculation of the average is incorrect.

One should divide the sum by the number of readings corresponding to each key, not the number of keys in each sub-array- i.e. divide by payload.length instead of array.length in the callback to Array.map().

### Original Array Modification

le_m is correct with the statement in the comment:

Above payload.reduce(...) mutates the first element of payload - that is probably unexpected behavior and could cause bugs further on.

This is because the initialValue parameter of Array.reduce() is omitted, which causes payload to be used as accumulator the first time 1. This can be observed by inspecting the values of currentIndex in the callback - for the sample data, it starts with 1 and ends with 2.

In order to not mutate the first element of payload, a deep clone of the first element can be used (e.g. with JSON.parse() and JSON.stringify()).

let initialValue = [];
if (payload.length) {
initialValue = JSON.parse(JSON.stringify(payload));
}
const result = payload.reduce((accumulator, currentValue, currentIndex) => {
/*...*/
}, initialValue)).map((currentValue, index, array) => {
return [currentValue, currentValue / array.length]
});


• Thanks @Sam Onela - I've ended up learning a lot from this fairly simple task. :) – L G Nov 25 '17 at 0:08

Sam’s answer including the word “matrix” got me thinking on using the transpose operation, resulting in a somewhat declarative solution:

// https://stackoverflow.com/a/17428705/280304
const transpose = mx => mx.map((col, i) => mx.map(row => row[i]))

const sumSecond = (acc, [k, v]) => acc + v

transpose(payload).map(row =>
[row, row.reduce(sumSecond, 0) / row.length])


(naturally, traversing and creating the arrays multiple times is going to be slower than doing everything in one go, but I feel it does bring clarity.)

...or constituting transpose(payload) with zip(...payload) where zip = (arr, ...arrs) => arr.map((val, i) => arrs.reduce((a, arr) => [...a, arr[i]], [val]))

• I added the context from your comment into your answer - please see the when should I comment section in Comment Everywhere – Sᴀᴍ Onᴇᴌᴀ Nov 27 '17 at 17:52