163

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?

dreftymac
  • 31,404
  • 26
  • 119
  • 182
firedrawndagger
  • 3,573
  • 10
  • 34
  • 51

17 Answers17

273

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;
 }
zcoop98
  • 2,590
  • 1
  • 18
  • 31
Tristan Reid
  • 5,844
  • 2
  • 26
  • 31
  • 21
    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" – Julian Mann Sep 16 '15 at 19:43
  • This is the absolute best answer for performance of large datasets (30+ columns / 100k rows). – cerd May 11 '16 at 15:03
  • There is a missing parenthesis in the first part. – hhh Apr 03 '17 at 13:00
  • 3
    Just wondering, when reduce checks the first element of the array won't `prev.Cost` be undefined? Or does it initiate as 0? – GrayedFox Oct 21 '17 at 13:11
  • addendum: sorry, just read [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce short answer: index starts at 1 if no initial value provided when using reduce – GrayedFox Oct 21 '17 at 13:17
  • 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. – Tristan Reid Oct 24 '17 at 18:39
  • This solution will throw exception if the array is empty. – Saheb Oct 03 '19 at 06:57
  • 1
    Good point @Saheb. I just edited so it will return null in that case. – Tristan Reid Oct 03 '19 at 13:40
  • 1
    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. – Tristan Reid Oct 03 '19 at 13:53
  • if you prefer not to alter the prototype, you can use `call` or `apply` as in `hasMin.call(myArray, 'Cost')`. – anakha Feb 18 '21 at 00:10
  • This was supremely helpful! Thanks – kbinstance Aug 10 '21 at 19:20
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);
ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • 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. – firedrawndagger Jan 14 '12 at 19:01
  • 2
    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. – J. Holmes Jan 14 '12 at 19:13
  • 1
    @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. – Rob W Jan 14 '12 at 19:17
  • I came here because I want the object itself returned. Not the lowest value, which was easy. Any suggestions? – Wilt Mar 11 '14 at 08:48
  • 1
    @Wilt 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; }` – Rob W Mar 11 '14 at 08:58
  • I'd argue creating temp variables and a for loop is overkill. The array is already created. See the top voted solution using reduce. – MilesMcBain Nov 24 '17 at 06:31
  • 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))` – Devin Prejean Jan 12 '18 at 16:53
  • 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);` – Milan Savaliya May 12 '19 at 06:07
  • 3
    This answer is very old, before ECMAScript 2015 (ES6) went out. Was right at the time, but now [that answer](https://stackoverflow.com/a/31844649/424498) is a better option. – Erick Petrucelli Sep 26 '20 at 19:06
55

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);
JuZDePeche
  • 679
  • 5
  • 15
  • 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 answer – Ntshembo Hlongwane Feb 25 '21 at 21:46
  • 10
    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 ;) – JuZDePeche Feb 27 '21 at 19:23
35

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]
katspaugh
  • 17,449
  • 11
  • 66
  • 103
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));
Rupert
  • 1,629
  • 11
  • 23
  • 30
    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. – Nitin Sep 01 '16 at 18:47
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'));
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
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.

faintsignal
  • 1,828
  • 3
  • 22
  • 30
J. Holmes
  • 18,466
  • 5
  • 47
  • 52
  • 4
    I have benchmarked our solutions: http://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. – Rob W Jan 14 '12 at 19:12
  • 2
    @RoBW oh I would totally expect your version to be *way* faster, I was just providing an alternate architectural implementation. :) – J. Holmes Jan 14 '12 at 19:14
  • @32bitkid I expected the same, but surprisongly, the method is almost as fast (after optimizing), as seen at test case 3 of the benchmark. – Rob W Jan 14 '12 at 19:16
  • 1
    @RobW i agree, I would not have expected that. I'm intrigued. :) Goes to show that you should always benchmark rather than assume. – J. Holmes Jan 14 '12 at 19:16
  • @RobW Just to be clear though, I think with more browser results, your implementation would consistently beat either the unoptimized and optimized versions. – J. Holmes Jan 14 '12 at 19:21
  • @32bitkid Using an array with **1000 keys**, your method (optimized) is **20% faster in Firefox 9! http://jsperf.com/comparison-of-numbers/3**. (but 81% slower in Chromium 17 :p). – Rob W Jan 14 '12 at 21:30
9

for Max

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

for min

Math.min.apply(Math, myArray.map(a => a.Cost));
Issa Lafi
  • 345
  • 3
  • 3
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>
Trent
  • 4,208
  • 5
  • 24
  • 46
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;
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
5

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);
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
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);
Deepak Sisodiya
  • 850
  • 9
  • 11
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);
James Moran
  • 624
  • 1
  • 7
  • 14
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)
Community
  • 1
  • 1
Jadli
  • 858
  • 1
  • 9
  • 17
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);
Dave
  • 3,073
  • 7
  • 20
  • 33
-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; 
Ben
  • 584
  • 9
  • 8
-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
sourcecode
  • 4,116
  • 1
  • 20
  • 14