问题
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?
回答1:
The fastest way, in this case, is looping through all elements, and compare it to the highest/lowest value, so far.
(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);
回答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;
}
回答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]
回答4:
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.
回答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));
回答6:
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);
回答7:
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;
回答8:
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);
回答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>
回答10:
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;
回答11:
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
来源:https://stackoverflow.com/questions/8864430/compare-javascript-array-of-objects-to-get-min-max