11

What is the best way to get the maximum and minimum values from a JavaScript array of objects?

Given:

var a = [{x:1,y:0},{x:-1,y:10},{x:12,y:20},{x:61,y:10}];
var minX = Infinity, maxX = -Infinity;
for( var x in a ){
  if( minX > a[x].x )
     minX = a[x].x;
  if( maxX < a[x].x )
     maxX = a[x].x;
}

Seems a bit clumsy. Is there a more elegant way, perhaps using dojo?

Martlark
  • 14,208
  • 13
  • 83
  • 99
  • 2
    [jsPerf test](http://jsperf.com/creative-min-max) with some of the algorithms below – dc5 Sep 14 '13 at 06:43
  • Thanks for the jsPerf test link. That is a great resource! – Martlark Sep 14 '13 at 09:34
  • Possible duplicate of [Compare JavaScript Array of Objects to Get Min / Max](https://stackoverflow.com/questions/8864430/compare-javascript-array-of-objects-to-get-min-max) –  Jun 22 '17 at 11:46

6 Answers6

10

It won't be more efficient, but just for grins:

var minX = Math.min.apply(Math, a.map(function(val) { return val.x; }));
var maxX = Math.max.apply(Math, a.map(function(val) { return val.x; }));

Or if you're willing to have three lines of code:

var xVals = a.map(function(val) { return val.x; });
var minX  = Math.min.apply(Math, xVals);
var maxX  = Math.max.apply(Math, xVals);
dc5
  • 12,341
  • 2
  • 35
  • 47
6

Use this example

var lowest = Number.POSITIVE_INFINITY;
var highest = Number.NEGATIVE_INFINITY;
var tmp;
for (var i=myArray.length-1; i>=0; i--) {
    tmp = myArray[i].Cost;
    if (tmp < lowest) lowest = tmp;
    if (tmp > highest) highest = tmp;
}
console.log(highest, lowest);
Brad
  • 159,648
  • 54
  • 349
  • 530
  • 1
    Not only is this identical to the question in principle, but it is actually less efficient. No need for that extra variable. – Brad Sep 14 '13 at 05:20
  • 2
    Are you talking about `tmp`? It is MORE efficient, because it is avoids repeated lookups. There's absolutely NEED for this variable. – Oleg V. Volkov Sep 14 '13 at 05:59
  • @Brad - unless I got the [jsPerf test case](http://jsperf.com/creative-min-max) wrong, this is actually quite a bit faster than even the version of the OP's algorithm that uses array indexes rather than a for/in loop. – dc5 Sep 14 '13 at 06:43
  • Ah, I understand now! I was not paying attention at all. I apologize for the downvote. – Brad Sep 14 '13 at 14:36
  • You can still make this like 35% faster by using constant integers instead of mixing integers and doubles, and by looping forwards http://jsperf.com/creative-min-max/5. Note that this jsperf is broken in general, all the code should be extracted into non-repeated functions and those functions warmed up properly before starting timing. – Esailija Sep 17 '13 at 08:46
4

You could use sort. This method modifies the original array, so you might need to clone it :

var b = [].concat(a); // clones "a"
b.sort(function (a, b) { return a.x - b.x; });
var min = b[0];
var max = b[b.length - 1];
  • 1
    Less efficiency. sort() is nlogn, which is slower than O(n) as OP posted. – zs2020 Sep 14 '13 at 14:53
  • @sza in this case, it appears the for-in loop on the array that the OP was using is slower than the sort method which surprised me as well. – dc5 Sep 14 '13 at 15:32
  • @dc5 Because of the number of the elements. The performance is generally based on how the efficiency is deteriorated as the number of elements increases. Try it with 1000+ objects. – zs2020 Sep 14 '13 at 15:39
  • @sza - I did and I understand the numbers, I should have been more clear that what surprised me was that even with the overhead of the copy and callbacks in the sort, it was still faster even at the smaller size. – dc5 Sep 14 '13 at 16:14
2

I know its a little too late, but for newer users you could use lodash. It makes the stuff a lot simpler.

var a = [{x:1,y:0},{x:-1,y:10},{x:12,y:20},{x:61,y:10}];

var X = [];
var Y = [];
a.map(function (val) {
    X.push(val.x);
    Y.push(val.y);
});

var minX = _.min(X);
var minY = _.min(Y);
var maxX = _.max(X);
var maxY = _.max(Y);

Or you could use .sort() to the task as procrastinator explained.

onFuryRoad
  • 375
  • 5
  • 12
1

Another idea is to calculate max/min by reducing the values to one value. This is exactly same as your version in terms of time complexity but a bit different way to think. (reduce() is supported since JavaScript 1.8.)

var getMax = function (field) {
    return a.reduce(function (acc, c) {
        return Math.max(c[field], acc);
    }, -Infinity);
}

var getMin = function (field) {
    return a.reduce(function (acc, c) {
        return Math.min(c[field], acc);
    }, Infinity);
}

console.log(getMax('x')) //61
console.log(getMin('x')) //-1
console.log(getMax('y')) //20
console.log(getMin('y')) //0
zs2020
  • 53,766
  • 29
  • 154
  • 219
0

You can use map functionality, but it is pretty much just a syntactic sugar around for. Any solution using reduce would be twice as slow as your "naive" because it would iterate array once for min value and once more for max. Your current solution is pretty much the best you can have in terms of performance. All you can do is to shave off some more lookups by caching them.

Oleg V. Volkov
  • 21,719
  • 4
  • 44
  • 68
  • A variation (for more apples to apples) of the reduce version by @sza is actually slightly faster than the OP's for/in loop: [jsPerf results](http://jsperf.com/creative-min-max) – dc5 Sep 14 '13 at 06:50
  • @user123444555621 The OP did mention "elegant" but also tagged the question [performance]. – faintsignal Feb 05 '18 at 20:35