Compare JavaScript Array of Objects to Get Min / Max
Asked Answered
M

17

174

I have an array of objects and I want to compare those objects on a specific object property. Here's my array:

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

I'd like to zero in on the "cost" specifically and a get a min and maximum value. I realize I can just grab the cost values and push them off into a javascript array and then run the Fast JavaScript Max/Min.

However is there an easier way to do this by bypassing the array step in the middle and going off the objects properties (in this case "Cost") directly?

Microwave answered 14/1, 2012 at 18:38 Comment(0)
T
70

One way is to loop through all elements and compare it to the highest/lowest value.

(Creating an array, invoking array methods is overkill for this simple operation).

 // There's no real number bigger than plus Infinity
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);
Tory answered 14/1, 2012 at 18:46 Comment(9)
This makes sense, I've been stuck with thinking about comparing data inside the array to each other instead of an external high/low number.Microwave
The only thing I would change is setting lowest and highest is a bit redundant. I would rather loop one less time and set lowest=highest=myArray[0] and then start the loop at 1.Nidorf
@32bitkid Good point. should be myArray[0].Cost, though. But, if there's no first element, an error will be thrown. So, an additional check is needed, possibly undoing the small performance boost.Tory
I came here because I want the object itself returned. Not the lowest value, which was easy. Any suggestions?Coley
@Coley Yes, maintain another variable that gets updated when you've found a lowest value, i.e. var lowestObject; for (...) and if (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }Tory
I'd argue creating temp variables and a for loop is overkill. The array is already created. See the top voted solution using reduce.Clinquant
If you're looking for an ES6 method that is a one liner keep going down. It's in a comment below. Copied here. Math.min(...myArray.map(o => o.Cost))Echinate
I think using sort method will provide min at the first position and max at the last position. myArray.sort((a, b) => a.Cost - b.Cost);Hannon
This answer is very old, before ECMAScript 2015 (ES6) went out. Was right at the time, but now that answer is a better option.Sapsago
H
282

Array.prototype.reduce() is good for stuff like this: to perform aggregate operations (like min, max, avg, etc.) on an array, and return a single result:

myArray.reduce(function(prev, curr) {
    return prev.Cost < curr.Cost ? prev : curr;
});

...or you can define that inner function with ES6 function syntax:

myArray.reduce((prev, curr) => prev.Cost < curr.Cost ? prev : curr);

If you want to be cute you can attach this to the Array prototype:

Array.prototype.hasMin = function(attrib) {
    return (this.length && this.reduce(function(prev, curr){ 
        return prev[attrib] < curr[attrib] ? prev : curr; 
    })) || null;
 }

Now you can just say:

myArray.hasMin('ID')  // result:  {"ID": 1, "Cost": 200}
myArray.hasMin('Cost')    // result: {"ID": 3, "Cost": 50}
myEmptyArray.hasMin('ID')   // result: null

Please note that if you intend to use this, it doesn't have full checks for every situation. If you pass in an array of primitive types, it will fail. If you check for a property that doesn't exist, or if not all the objects contain that property, you will get the last element. This version is a little more bulky, but has those checks:

Array.prototype.hasMin = function(attrib) {
    const checker = (o, i) => typeof(o) === 'object' && o[i]
    return (this.length && this.reduce(function(prev, curr){
        const prevOk = checker(prev, attrib);
        const currOk = checker(curr, attrib);
        if (!prevOk && !currOk) return {};
        if (!prevOk) return curr;
        if (!currOk) return prev;
        return prev[attrib] < curr[attrib] ? prev : curr; 
    })) || null;
 }
Headspring answered 5/8, 2015 at 23:51 Comment(10)
Best answer in my opinion. It doesn't modify the array and its far more concise than the answer that says "Creating an array, invoking array methods is overkill for this simple operation"Becker
This is the absolute best answer for performance of large datasets (30+ columns / 100k rows).Longfellow
There is a missing parenthesis in the first part.Tremain
Just wondering, when reduce checks the first element of the array won't prev.Cost be undefined? Or does it initiate as 0?Cattan
addendum: sorry, just read [this](developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… short answer: index starts at 1 if no initial value provided when using reduceCattan
Yes - that's an important point: if you have a reduce that uses an accumulator that's a different type than the elements of the original array, it's really important to provide an initial value, otherwise the accumulator will start with the wrong type.Headspring
This solution will throw exception if the array is empty.Involuted
Good point @Saheb. I just edited so it will return null in that case.Headspring
I also added some checks for other inputs that could cause issues, like non-objects, or if that property was missing from some of the objects. Ultimately it's getting a little heavy-handed for most cases I think.Headspring
if you prefer not to alter the prototype, you can use call or apply as in hasMin.call(myArray, 'Cost').Gratulation
T
70

One way is to loop through all elements and compare it to the highest/lowest value.

(Creating an array, invoking array methods is overkill for this simple operation).

 // There's no real number bigger than plus Infinity
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);
Tory answered 14/1, 2012 at 18:46 Comment(9)
This makes sense, I've been stuck with thinking about comparing data inside the array to each other instead of an external high/low number.Microwave
The only thing I would change is setting lowest and highest is a bit redundant. I would rather loop one less time and set lowest=highest=myArray[0] and then start the loop at 1.Nidorf
@32bitkid Good point. should be myArray[0].Cost, though. But, if there's no first element, an error will be thrown. So, an additional check is needed, possibly undoing the small performance boost.Tory
I came here because I want the object itself returned. Not the lowest value, which was easy. Any suggestions?Coley
@Coley Yes, maintain another variable that gets updated when you've found a lowest value, i.e. var lowestObject; for (...) and if (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }Tory
I'd argue creating temp variables and a for loop is overkill. The array is already created. See the top voted solution using reduce.Clinquant
If you're looking for an ES6 method that is a one liner keep going down. It's in a comment below. Copied here. Math.min(...myArray.map(o => o.Cost))Echinate
I think using sort method will provide min at the first position and max at the last position. myArray.sort((a, b) => a.Cost - b.Cost);Hannon
This answer is very old, before ECMAScript 2015 (ES6) went out. Was right at the time, but now that answer is a better option.Sapsago
H
63

Using Math.min and Math.max:

var myArray = [
    { id: 1, cost: 200},
    { id: 2, cost: 1000},
    { id: 3, cost: 50},
    { id: 4, cost: 500}
]


var min = Math.min(...myArray.map(item => item.cost));
var max = Math.max(...myArray.map(item => item.cost));

console.log("min: " + min);
console.log("max: " + max);
Homesick answered 4/5, 2020 at 14:44 Comment(2)
I know it must be to late to ask but why do we need the spread operator like you did for myArray.map() I would appreciate the answerCrudity
Because the function Math.max takes multiple parameters and not an array. The spread operator will transform the array into a "list" of parameters. e.g.: Math.max(...[1,5,9]) is the equivalent of Math.max(1, 5, 9). Without the spread operator, Math.max(myArray) will return NaN (not a number) since the function expects multiple number parameters. I hope it is not to late to reply @NtshemboHlongwane ;)Homesick
M
36

Use sort, if you don't care about the array being modified.

myArray.sort(function (a, b) {
    return a.Cost - b.Cost
})

var min = myArray[0],
    max = myArray[myArray.length - 1]
Monodrama answered 14/1, 2012 at 18:43 Comment(3)
a full sort isn't the fastest way to find min/max but i guess it'll work.Nidorf
Just be aware that this will modify myArray, which may not be expected.Debauchee
Sorting an array is slower than traversing it. Sort complexity: O(nlog(n)), traversing an array: O(n)Oculus
I
31

Use Math functions and pluck out the values you want with map.

Here is the jsbin:

https://jsbin.com/necosu/1/edit?js,console

var myArray = [{
    "ID": 1,
    "Cost": 200
  }, {
    "ID": 2,
    "Cost": 1000
  }, {
    "ID": 3,
    "Cost": 50
  }, {
    "ID": 4,
    "Cost": 500
  }],

  min = Math.min.apply(null, myArray.map(function(item) {
    return item.Cost;
  })),
  max = Math.max.apply(null, myArray.map(function(item) {
    return item.Cost;
  }));

console.log('min', min);//50
console.log('max', max);//1000

UPDATE:

If you want to use ES6:

var min = Math.min.apply(null, myArray.map(item => item.Cost)),
    max = Math.max.apply(null, myArray.map(item => item.Cost));
Impedance answered 30/9, 2015 at 10:51 Comment(1)
In ES6 using Spread Operator, we no longer need apply. Simply say - Math.min(...myArray.map(o => o.Cost)) for finding the minimum and Math.max(...myArray.map(o => o.Cost)) for finding the maximum.Derry
U
17

Try (a is array, f is field to compare)

let max= (a,f)=> a.reduce((m,x)=> m[f]>x[f] ? m:x);
let min= (a,f)=> a.reduce((m,x)=> m[f]<x[f] ? m:x);

let max= (a,f)=> a.reduce((m,x)=> m[f]>x[f] ? m:x);
let min= (a,f)=> a.reduce((m,x)=> m[f]<x[f] ? m:x);

// TEST

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

console.log('Max Cost', max(myArray, 'Cost'));
console.log('Min Cost', min(myArray, 'Cost'));

console.log('Max ID', max(myArray, 'ID'));
console.log('Min ID', min(myArray, 'ID'));
Unworldly answered 17/12, 2019 at 10:22 Comment(2)
Love this answer, so compact and easy to use.Nyala
I +1 what Dean said and feel it's VERY quickly to reason about in a glance.Dystopia
N
16

I think Rob W's answer is really the right one (+1), but just for fun: if you wanted to be "clever", you could do something like this:

var myArray = 
[
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

function finder(cmp, arr, attr) {
    var val = arr[0][attr];
    for(var i=1;i<arr.length;i++) {
        val = cmp(val, arr[i][attr])
    }
    return val;
}

alert(finder(Math.max, myArray, "Cost"));
alert(finder(Math.min, myArray, "Cost"));

or if you had a deeply nested structure, you could get a little more functional and do the following:

var myArray = 
[
    {"ID": 1, "Cost": { "Wholesale":200, Retail: 250 }},
    {"ID": 2, "Cost": { "Wholesale":1000, Retail: 1010 }},
    {"ID": 3, "Cost": { "Wholesale":50, Retail: 300 }},
    {"ID": 4, "Cost": { "Wholesale":500, Retail: 1050 }}
]

function finder(cmp, arr, getter) {
    var val = getter(arr[0]);
    for(var i=1;i<arr.length;i++) {
        val = cmp(val, getter(arr[i]))
    }
    return val;
}

alert(finder(Math.max, myArray, function(x) { return x.Cost.Wholesale; }));
alert(finder(Math.min, myArray, function(x) { return x.Cost.Retail; }));

These could easily be curried into more useful/specific forms.

Nidorf answered 14/1, 2012 at 19:0 Comment(6)
I have benchmarked our solutions: jsperf.com/comparison-of-numbers. After optimizing your code (see the benchmark), the performance of both methods are similar. Without optimization, my method is 14x faster.Tory
@RoBW oh I would totally expect your version to be way faster, I was just providing an alternate architectural implementation. :)Nidorf
@32bitkid I expected the same, but surprisongly, the method is almost as fast (after optimizing), as seen at test case 3 of the benchmark.Tory
@RobW i agree, I would not have expected that. I'm intrigued. :) Goes to show that you should always benchmark rather than assume.Nidorf
@RobW Just to be clear though, I think with more browser results, your implementation would consistently beat either the unoptimized and optimized versions.Nidorf
@32bitkid Using an array with 1000 keys, your method (optimized) is 20% faster in Firefox 9! jsperf.com/comparison-of-numbers/3. (but 81% slower in Chromium 17 :p).Tory
H
9

for Max

Math.max.apply(Math, myArray.map(a => a.Cost));

for min

Math.min.apply(Math, myArray.map(a => a.Cost));
Hydrastinine answered 28/12, 2019 at 13:52 Comment(0)
F
7

This can be achieved with lodash's minBy and maxBy functions.

Lodash's minBy and maxBy documentation

_.minBy(array, [iteratee=_.identity])

_.maxBy(array, [iteratee=_.identity])

These methods accept an iteratee which is invoked for each element in array to generate the criterion by which the value is ranked. The iteratee is invoked with one argument: (value).

Solution

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

const minimumCostItem = _.minBy(myArray, "Cost");

console.log("Minimum cost item: ", minimumCostItem);

// Getting the maximum using a functional iteratee
const maximumCostItem = _.maxBy(myArray, function(entry) {
  return entry["Cost"];
});

console.log("Maximum cost item: ", maximumCostItem);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Fredelia answered 30/7, 2019 at 17:45 Comment(0)
A
6

For a concise, modern solution, one can perform a reduce operation over the array, keeping track of the current minimum and maximum values, so the array is only iterated over once (which is optimal).

let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
   [Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);

Demo:

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]
let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
   [Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);
console.log("Min cost:", min);
console.log("Max cost:", max);
Arouse answered 19/8, 2020 at 20:5 Comment(0)
S
5

Using Array.prototype.reduce(), you can plug in comparator functions to determine the min, max, etc. item in an array.

var items = [
  { name : 'Apple',  count : 3  },
  { name : 'Banana', count : 10 },
  { name : 'Orange', count : 2  },
  { name : 'Mango',  count : 8  }
];

function findBy(arr, key, comparatorFn) {
  return arr.reduce(function(prev, curr, index, arr) { 
    return comparatorFn.call(arr, prev[key], curr[key]) ? prev : curr; 
  });
}

function minComp(prev, curr) {
  return prev < curr;
}

function maxComp(prev, curr) {
  return prev > curr;
}

document.body.innerHTML  = 'Min: ' + findBy(items, 'count', minComp).name + '<br />';
document.body.innerHTML += 'Max: ' + findBy(items, 'count', maxComp).name;
Spicebush answered 16/11, 2015 at 19:6 Comment(0)
U
3

This is more better solution

    var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
    ]
    var lowestNumber = myArray[0].Cost;
    var highestNumber = myArray[0].Cost;

    myArray.forEach(function (keyValue, index, myArray) {
      if(index > 0) {
        if(keyValue.Cost < lowestNumber){
          lowestNumber = keyValue.Cost;
        }
        if(keyValue.Cost > highestNumber) {
          highestNumber = keyValue.Cost;
        }
      }
    });
    console.log('lowest number' , lowestNumber);
    console.log('highest Number' , highestNumber);
Unbelievable answered 5/8, 2015 at 17:46 Comment(0)
T
3

Adding onto Tristan Reid's answer (+ using es6), you could create a function that accepts a callback, which will contain the operator you want to be applied to the prev and curr:

const compare = (arr, key, callback) => arr.reduce((prev, curr) =>
    (callback(prev[key], curr[key]) ? prev : curr), {})[key];

    // remove `[key]` to return the whole object

Then you could simply call it using:

const costMin = compare(myArray, 'Cost', (a, b) => a < b);
const costMax = compare(myArray, 'Cost', (a, b) => a > b);
Trichromat answered 7/5, 2017 at 13:52 Comment(0)
C
1

we can solve problem by two approach both method is already explained above but the performance test was missing so completing that one

1, native java-script way
2, first sort object then it easy to get min max from sorted obj

i also test performance of both tow approach

you can also run and test performance... Happy coding (:

//first approach 

var myArray = [
    {"ID": 1, "Cost": 200},
    {"ID": 2, "Cost": 1000},
    {"ID": 3, "Cost": 50},
    {"ID": 4, "Cost": 500}
]

var t1 = performance.now();;

let max=Math.max.apply(Math, myArray.map(i=>i.Cost))

let min=Math.min.apply(Math, myArray.map(i=>i.Cost))

var t2   = performance.now();;

console.log("native fuction took " + (t2 - t1) + " milliseconds.");

console.log("max Val:"+max)
console.log("min Val:"+min)

//  Second approach:


function sortFunc (a, b) {
    return a.Cost - b.Cost
} 

var s1 = performance.now();;
sortedArray=myArray.sort(sortFunc)


var minBySortArray = sortedArray[0],
    maxBySortArray = sortedArray[myArray.length - 1]
    
var s2   = performance.now();;
 console.log("sort funciton took  " + (s2 - s1) + " milliseconds.");  
console.log("max ValBySortArray :"+max)
console.log("min Val BySortArray:"+min)
Creepie answered 28/12, 2019 at 14:18 Comment(0)
P
1
max = totalAVG.reduce(function (a, b) { return Math.max(a, b)}, -Infinity);

min = totalAVG.reduce(function (a, b) {return Math.min(a, b)}, Infinity);
Pilarpilaster answered 14/3, 2022 at 11:48 Comment(0)
T
-1

Another one, similar to Kennebec's answer, but all in one line:

maxsort = myArray.slice(0).sort(function (a, b) { return b.ID - a.ID })[0].ID; 
Turbofan answered 9/1, 2014 at 18:43 Comment(0)
U
-1

You can use built-in Array object to use Math.max/Math.min instead:

var arr = [1,4,2,6,88,22,344];

var max = Math.max.apply(Math, arr);// return 344
var min = Math.min.apply(Math, arr);// return 1
Unmake answered 19/7, 2014 at 13:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.