'Compare JavaScript Array of Objects to Get Min / Max
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?
Solution 1:[1]
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);
Solution 2:[2]
The reduce is good for stuff like this: to perform aggregate operations (like min, max, avg, etc.) on an array of objects, 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:
(prev, curr) => prev.Cost < curr.Cost ? prev : curr
If you want to be cute you can attach this to array:
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;
}
Solution 3:[3]
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]
Solution 4:[4]
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);
Solution 5:[5]
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));
Solution 6:[6]
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.
Solution 7:[7]
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'));
Solution 8:[8]
for Max
Math.max.apply(Math, myArray.map(a => a.Cost));
for min
Math.min.apply(Math, myArray.map(a => a.Cost));
Solution 9:[9]
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>
Solution 10:[10]
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;
Solution 11:[11]
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);
Solution 12:[12]
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);
Solution 13:[13]
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);
Solution 14:[14]
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)
Solution 15:[15]
max = totalAVG.reduce(function (a, b) { return Math.max(a, b)}, -Infinity);
min = totalAVG.reduce(function (a, b) {return Math.min(a, b)}, Infinity);
Solution 16:[16]
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;
Solution 17:[17]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
