I'm trying to do this in a functional way so although there are some methods I can already think of, I suspect there's a more elegant map/reduce-style technique.
Given an array like this:
[0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21,
507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,
913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,
1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791]
I want to pute the difference between each item and its following value, eg:
[47.22, 25.43, 54.28] // etc
(these values are timestamps for chat messages – I want to effectively see how long gaps were between messages, and then calculate the average for this user).
I was looking at reduce()
but struggling to see how to make it do what I need. Is there an elegant way to do this or is my best bet a for
loop which looks ahead to the next array item and does the calculation there?
I'm trying to do this in a functional way so although there are some methods I can already think of, I suspect there's a more elegant map/reduce-style technique.
Given an array like this:
[0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21,
507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,
913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,
1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791]
I want to pute the difference between each item and its following value, eg:
[47.22, 25.43, 54.28] // etc
(these values are timestamps for chat messages – I want to effectively see how long gaps were between messages, and then calculate the average for this user).
I was looking at reduce()
but struggling to see how to make it do what I need. Is there an elegant way to do this or is my best bet a for
loop which looks ahead to the next array item and does the calculation there?
-
Why not use a
forEach
? Also, I'm interested to see what approaches you have already tried. – evolutionxbox Commented Oct 25, 2016 at 9:27 - what about negative deltas? – Nina Scholz Commented Oct 25, 2016 at 9:36
- @MattAndrews For puting average how do you propose to handle negative differences? – kukkuz Commented Oct 25, 2016 at 14:11
- Good point – for now there shouldn't be any as these numbers are ordered sequentially, but you never know with data... – Matt Andrews Commented Oct 25, 2016 at 14:16
5 Answers
Reset to default 4Another solution using reduce
. This function returns a sensible NaN
result when the average is not putable.
This data putes to an incorrect answer of 0.7858965517241373
because the input data is unordered which makes no sense. If you sort the data you get a more sensible 31.48965517241379
seconds average delta.
const averageDelta = ([x,...xs]) => {
if (x === undefined)
return NaN
else
return xs.reduce(
([acc, last], x) => [acc + (x - last), x],
[0, x]
) [0] / xs.length
};
let timestamps = [
0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21,
507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,
913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,
1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791
]
console.log(averageDelta(timestamps)) // 0.7858965517241373
console.log(averageDelta(timestamps.sort())) // 31.48965517241379
But you tagged this question with functional-programming
so you're probably looking for a better solution. The above answer is fine but it mixes concerns of several basic arithmetic putations. averageDelta
is puting difference, sum, and mean average all in one function – what a mess!
Below we will implement a function for each of those things separately so that we don't have to think so hard. You'll also notice I took care to ensure each function is a total function which works on all inputs of its domain. This means average
and delta
will return sensible results for empty arrays, single-number arrays, and of course arrays with more than one number.
const add = (x,y) =>
x + y
const sum = xs =>
xs.reduce(add, 0)
const average = xs =>
xs[0] === undefined ? NaN : sum(xs) / xs.length
const delta = ([x,...xs]) =>
xs.reduce(([acc, last], x) => [[...acc, x-last], x], [[], x]) [0]
let timestamps = [
0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21,
507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,
913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,
1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791
]
console.log(average(delta([]))) // NaN
console.log(average(delta([1]))) // NaN
console.log(average(delta(timestamps))) // 0.7858965517241373
console.log(average(delta(timestamps.sort()))) // 31.48965517241379
Warning:
timestamps.sort()
usesArray.prototype.sort
which mutates the original array. Considering sorting wasn't talked about in the original post, I'll leave that as a problem for you to solve, if it even is one.
You can do this with reduce()
like this.
var data = [0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21,
507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,
913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,
1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791];
var result = data.reduce(function(r, e, i) {
if(data[i+1]) r.push(Number((data[i+1] - e).toFixed(2)));
return r;
}, [])
console.log(result)
You could use Array#reduce
and use only the values with predecessor.
var array = [0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21, 507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94, 913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03, 1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791],
delta = array.reduce(function (r, a, i, aa) {
i && r.push(a - aa[i - 1]);
return r;
}, []),
average = delta.reduce(function (a, b) { return a + b; }) / delta.length;
console.log(delta);
console.log(average);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use map
and return difference between current and next item
var arr = [0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21,
507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,
913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,
1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791];
var output = arr.map( function( item, index, arr ){
return (arr[index+1] - arr[index]);
});
output.splice(-1,1); //remove last item, it is NaN since it is a difference between undefined and 22.791
console.log("Diff is", output);
var average = output.reduce( function(curr,prev){ return curr+prev; })/output.length;
console.log("average", average);
Using a forEach
loop to add the difference between adjacent elements into a new array:
var arr = [22, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21, 507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791];
var result = {array:[], sum: 0, current: 0};
arr.forEach(function(element, index, array) {
this.current = ((array[index + 1] - element) || 0);
this.sum += this.current;
this.array.push(this.current.toFixed(3));
}, result);
console.log(result.array);
console.log("Average: ", (result.sum / result.array.length).toFixed(3));
.as-console-wrapper{top:0;max-height:100%!important;}
EDIT:
Credit to @naomik for suggesting this - better to use reduce
by keeping all mutations local to the callback
var arr = [0, 47.22, 72.65, 126.93, 155.02, 307.46, 410.02, 417.21, 507.86, 510.466, 580.88, 661.29, 685.14, 695.86, 780.94,913.2, 1352.33, 1382.99, 1435.73, 1462.03, 1495.38, 1518.03,1523.58, 1544.3, 1591.21, 1612.03, 1665.99, 1672.62, 11.02, 22.791];
var result = arr.reduce(function(acc, element, index, array) {
acc.sum += element - acc.prev;
index && acc.array.push((element - acc.prev).toFixed(3));
acc.prev = element;
return acc;
}, {array:[], sum: 0, prev: arr[0]});
console.log(result.array);
console.log("Average: ", (result.sum / result.array.length).toFixed(3));
.as-console-wrapper{top:0;max-height:100%!important;}