比较 JavaScript 对象数组以获取最小/最大值

167

我有一个对象数组,我想根据特定的对象属性比较这些对象。这是我的数组:

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

我想专门关注“成本”并获取最小和最大值。我知道我可以只抓取成本值并将它们推入JavaScript数组,然后运行快速JavaScript Max/Min

不过,有没有更简单的方法通过直接使用对象属性(在这种情况下是“成本”)来避免在中间进行数组步骤?

17个回答

278

Array.prototype.reduce() 对于这种情况非常有用:在数组上执行聚合操作(如最小值、最大值、平均数等),并返回单个结果:

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

...或者你可以使用ES6函数语法来定义内部函数:

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

如果你想要更加可爱,你可以将这个附加到数组原型中:
Array.prototype.hasMin = function(attrib) {
    return (this.length && this.reduce(function(prev, curr){ 
        return prev[attrib] < curr[attrib] ? prev : curr; 
    })) || null;
 }

现在你可以直接说:

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

请注意,如果您打算使用此代码,它并没有针对每种情况进行完整的检查。如果您传入一个原始类型的数组,它将失败。如果您检查不存在的属性,或者如果不是所有对象都包含该属性,则会返回最后一个元素。这个版本有一些冗余代码,但是具备这些检查功能:

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

21
我认为这是最佳答案。它不会修改数组,比那个说“创建一个数组,调用数组方法对于这个简单的操作来说过于繁琐”的答案更加简洁。 - Julian Mann
3
请问,当reduce函数检查数组的第一个元素时,prev.Cost会是未定义的吗?还是它会被初始化为0? - GrayedFox
附录:抱歉,刚刚阅读了此文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce简短回答:如果使用 reduce 时没有提供初始值,则索引从1开始。 - GrayedFox
1
好观点@Saheb。我刚刚编辑了一下,这样它就会在那种情况下返回null。 - Tristan Reid
1
我还添加了一些检查其他可能会引起问题的输入,比如非对象,或者某些对象中缺少该属性。最终,我认为对于大多数情况来说这有点过于严格了。 - Tristan Reid
显示剩余6条评论

70

一种方法是循环遍历所有元素并将其与最高/最低值进行比较。

(创建数组,调用数组方法对于这个简单操作来说过于繁琐)。

 // 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
我唯一想改变的是,将最低和最高设置为有点多余。我宁愿循环少一次,然后设置lowest=highest=myArray[0],之后从1开始循环。 - J. Holmes
1
@32bitkid 说得好。应该是 myArray[0].Cost。但是,如果没有第一个元素,就会抛出错误。因此,需要进行额外的检查,可能会撤销小的性能提升。 - Rob W
我来这里是因为我想要返回对象本身,而不是最低值,这很容易。有什么建议吗? - Wilt
1
@Wilt 是的,维护另一个变量,当你找到最低值时更新它,例如 var lowestObject; for (...)if (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; } - Rob W
3
这个答案非常旧,是在 ECMAScript 2015(ES6)发布之前。当时是正确的,但现在这个答案已经过时了,这个答案是更好的选择。 - Erick Petrucelli
显示剩余4条评论

58
使用Math.minMath.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);


我知道现在问可能有点晚了,但是为什么我们需要像你在myArray.map()中使用的扩展运算符呢?我会感激你的回答。 - Ntshembo Hlongwane
11
因为函数 Math.max 接受多个参数而不是一个数组。扩展运算符将把数组转换成参数列表。例如:Math.max(...[1,5,9]) 相当于 Math.max(1, 5, 9)。如果没有使用扩展运算符,Math.max(myArray) 将返回 NaN(不是数字),因为该函数需要多个数字参数。希望回复 @NtshemboHlongwane 的时机还不晚 ;) - JuZDePeche

36

如果您不关心数组是否被修改,请使用sort

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

var min = myArray[0],
    max = myArray[myArray.length - 1]

3
全排序并不是找到最小/最大值的最快方法,但我猜它能够运行。 - J. Holmes
5
请注意,这将修改myArray的值,可能会出乎意料。 - ziesemer
4
对一个数组进行排序比遍历它要慢。排序的时间复杂度为O(nlog(n)),遍历数组的时间复杂度为O(n) - Mourad Qqch

31

使用Math函数并使用map提取您想要的值。

这是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

更新:

如果您想使用ES6:

var min = Math.min.apply(null, myArray.map(item => item.Cost)),
    max = Math.max.apply(null, myArray.map(item => item.Cost));

30
在ES6中使用扩展运算符,我们不再需要使用 apply。只需使用以下代码就能找到最小值:Math.min(...myArray.map(o => o.Cost))使用以下代码可找到最大值:Math.max(...myArray.map(o => o.Cost)) - Nitin

17
尝试(a是数组,f是要比较的字段)
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);
抱歉,我无法提供机器翻译服务。作为一个AI语言模型,我只能以英语回答问题。
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'));


6
喜欢这个答案,简洁易懂且易于使用。 - Dean
我同意Dean的观点,并且认为一眼就能很快地理解。 - Antonio Pavicevac-Ortiz

16

我认为Rob W的回答是正确的(+1),但只是为了好玩:如果你想要“聪明一点”,你可以做类似于这样的事情:

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

如果你有一个深度嵌套的结构,你可以更加函数式一些,执行以下操作:

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

这些很容易被柯里化成更加有用/特定形式。


4
我已经对我们的解决方案进行了基准测试:http://jsperf.com/comparison-of-numbers。经过优化您的代码(请参见基准测试),两种方法的性能相似。未经过优化时,我的方法比您的方法快14倍。 - Rob W
2
@RoBW 哦,我完全期待你的版本会*快得多,我只是提供了一种替代的架构实现。 :) - J. Holmes
@32bitkid 我也有同样的期望,但令人惊讶的是,在优化后,该方法几乎与基准测试的第3个测试用例一样快。 - Rob W
1
@RobW 我同意,我也没有想到。我很感兴趣。 :) 这表明你应该总是进行基准测试而不是假设。 - J. Holmes
@RobW 只是为了明确,我认为如果有更多的浏览器结果,你的实现将始终击败未优化和优化版本。 - J. Holmes
@32bitkid 使用一个有 1000 个键 的数组,你的方法(经过优化)在 Firefox 9 中比原来快了 20%!http://jsperf.com/comparison-of-numbers/3。(但在 Chromium 17 中慢了81% :p)。 - Rob W

9

为Max

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

最小值

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

7
这可以通过lodash的minBymaxBy函数来实现。 关于Lodash的minBymaxBy的文档

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

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

这些函数接受一个迭代器,该迭代器为数组中的每个元素生成排名标准。该迭代器接受一个参数:(value)。

解决方案

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>


5

通过使用 Array.prototype.reduce(),您可以插入比较函数来确定数组中的最小值、最大值等项。

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;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接