简体   繁体   中英

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?

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;
 }

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);

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]

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));

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.

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);

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'));

for Max

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

for min

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

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;

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>

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);

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);

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);

max = totalAVG.reduce(function (a, b) { return Math.max(a, b)}, -Infinity);

min = totalAVG.reduce(function (a, b) {return Math.min(a, b)}, Infinity);

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)

另一个,类似于 Kennebec 的回答,但都在一行中:

maxsort = myArray.slice(0).sort(function (a, b) { return b.ID - a.ID })[0].ID; 

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM