1
\$\begingroup\$

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][1] += c[1]
        return accumulator
    }, accumulator)

 }).map((currentValue, index, array) => {
    return [currentValue[0], currentValue[1] / array.length]
})
\$\endgroup\$
3
  • 2
    \$\begingroup\$ Above payload.reduce(...) mutates the first element of payload - that is probably unexpected behavior and could cause bugs further on. \$\endgroup\$
    – le_m
    Commented Nov 23, 2017 at 18:43
  • \$\begingroup\$ Thanks @le_m - It was unexpected, and did cause bugs. A lot of head scratching yesterday until I read your comment. \$\endgroup\$
    – L G
    Commented Nov 25, 2017 at 0:04
  • 2
    \$\begingroup\$ Please see What to do when someone answers. I have rolled back Rev 6 → 5. \$\endgroup\$ Commented Nov 25, 2017 at 0:11

2 Answers 2

1
\$\begingroup\$

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[0] 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[0]));
}
const result = payload.reduce((accumulator, currentValue, currentIndex) => { 
    /*...*/ 
}, initialValue)).map((currentValue, index, array) => {
    return [currentValue[0], currentValue[1] / array.length]
});

1https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Parameters

\$\endgroup\$
1
  • \$\begingroup\$ Thanks @Sam Onela - I've ended up learning a lot from this fairly simple task. :) \$\endgroup\$
    – L G
    Commented Nov 25, 2017 at 0:08
1
\$\begingroup\$

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[0].map((col, i) => mx.map(row => row[i]))

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

transpose(payload).map(row =>
  [row[0][0], 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]))

from A simple ES6 zip function on a Github Gist.

\$\endgroup\$
1
  • \$\begingroup\$ I added the context from your comment into your answer - please see the when should I comment section in Comment Everywhere \$\endgroup\$ Commented Nov 27, 2017 at 17:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.