最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How can I compute the difference between array values, then average them? - Stack Overflow

programmeradmin4浏览0评论

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?

Share Improve this question asked Oct 25, 2016 at 9:24 Matt AndrewsMatt Andrews 2,8783 gold badges34 silver badges56 bronze badges 4
  • 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
Add a ment  | 

5 Answers 5

Reset to default 4

Another 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() uses Array.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;}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论