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


95

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

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

我想将“成本”具体为零,并获得最小值和最大值。我意识到我可以获取成本值并将其推入javascript数组,然后运行Fast JavaScript Max / Min

但是,有没有一种更简单的方法可以通过绕过中间的数组步骤并直接关闭对象属性(在这种情况下为“成本”)来做到这一点?

Answers:


53

在这种情况下,最快的方法是遍历所有元素,并将其与到目前为止的最高/最低值进行比较。

(创建数组,调用数组方法对于这种简单的操作来说是多余的)。

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

这是有道理的,我一直在考虑比较数组内部的数据而不是比较外部的高/低数字。
firedrawndagger 2012年

1
我唯一要更改的是将最低和最高设置为一点冗余。我宁愿一周更少的时间和集合lowest=highest=myArray[0],然后开始循环,在1
J.霍姆斯

1
@ 32bitkid好点。应该是myArray[0].Cost。但是,如果没有第一个元素,则会引发错误。因此,需要进行额外的检查,可能会抵消较小的性能提升。
罗伯W

我来这里是因为我希望对象本身返回。不是最低值,这很容易。有什么建议?
2014年

1
@Wilt是的,请维护另一个变量,该变量在找到最小值后即会更新:var lowestObject; for (...)if (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Rob W

159

reduce适合于这样的事情:对一组对象执行聚合操作(如min,max,avg等),并返回一个结果:

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

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

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

13
我认为最好的答案。它不会修改数组,而且比“创建数组,调用数组方法对这种简单操作来说是过分的杀伤力”的答案要简洁得多
朱利安·曼

对于大型数据集(30+列/ 10万行)的性能,这是绝对最佳的答案。
cerd

2
只是想知道,什么时候reduce检查数组的第一个元素不会prev.Cost是未定义的?还是以0开头?
GrayedFox

1
好点@Saheb。我刚刚编辑过,因此在这种情况下它将返回null。
特里斯坦·里德

1
我还添加了一些检查,以检查可能导致问题的其他输入,例如非对象,或者某些对象是否缺少该属性。最终,在我认为的大多数情况下,它变得有些笨拙。
特里斯坦·里德

26

使用sort,如果你不关心的阵列进行修改。

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

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

3
完整排序并不是找到最小/最大的最快方法,但我想它会起作用。
J. Holmes

4
请注意,这将修改myArray,这可能是无法预期的。
ziesemer

对数组进行排序要比遍历数组慢。排序复杂度:O(nlog(n)),遍历数组:O(n)
Mourad Qqch '19

17

使用Math函数并使用提取所需的值map

这是jsbin:

https://jsbin.com/necosu/1/edit?js,控制台

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

14
在使用Spread Operator的ES6中,我们不再需要apply。简单地说- Math.min(...myArray.map(o => o.Cost))寻找最小值和 Math.max(...myArray.map(o => o.Cost))最大值。
Nitin

13

我认为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
我已经对我们的解决方案进行了基准测试:jsperf.com/comparison-of-numbers。优化代码后(请参阅基准),两种方法的性能相似。没有优化,我的方法速度提高了14倍。
罗伯W

2
@RoBW哦,我会完全指望你的版本是这样快,我只是提供一个替代体系结构实现。:)
J. Holmes 2012年

@ 32bitkid我期望相同,但令人惊讶的是,该方法几乎快(优化后),如基准测试的第3个案例所示。
罗布W

1
@RobW我同意,我不会期望的。我很感兴趣 :)表明您应该始终进行基准测试而不是假设。
J. Holmes

@RobW不过,需要明确的是,我认为随着浏览器结果的增多,您的实现将始终优于未优化和优化的版本。
J. Holmes


5

尝试(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);


2
喜欢这个答案,因此紧凑且易于使用。
院长

3

使用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;


3

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


2

这是更好的解决方案

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

2

添加到Tristan Reid的答案(使用es6的+)上,您可以创建一个接受回调的函数,该函数包含要应用于prevand 的运算符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

然后,您可以使用以下命令简单地调用它:

const costMin = compare(myArray, 'Cost', (a, b) => a < b);
const costMax = compare(myArray, 'Cost', (a, b) => a > b);

2

这可以通过lodash minBymaxBy函数来实现。

Lodash minBymaxBy文档

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

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

这些方法接受一个iteratee,它会为数组中的每个元素调用以生成用于对值进行排名的条件。使用一个参数调用iteratee :(值)。

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>


0

我们可以通过两种方法解决问题上面已经介绍了两种方法,但是缺少性能测试,因此请完成一个

1,本机Java脚本方式
2,首先对对象进行排序,然后很容易从已排序的obj中获取min max

我还测试了两种方法的性能

您还可以运行并测试性能...快乐编码(:

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


0

对于一种简洁,现代的解决方案,可以reduce对数组执行操作,并跟踪当前的最小值和最大值,因此仅将数组迭代一次(最佳)。

let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
   [Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);

演示:


-2

另一个类似于肯尼贝克的答案,但全部在一行中:

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

-2

您可以使用内置的Array对象来代替使用Math.max / Math.min:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.