【问题标题】:Compare JavaScript Array of Objects to Get Min / Max比较 JavaScript 对象数组以获取最小值/最大值
【发布时间】:2012-02-10 11:23:21
【问题描述】:

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

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

我想特别将“成本”归零并获得最小值和最大值。我意识到我可以获取成本值并将它们推送到 javascript 数组中,然后运行 ​​Fast JavaScript Max/Min

但是,有没有更简单的方法可以绕过中间的数组步骤并直接关闭对象属性(在本例中为“Cost”)?

【问题讨论】:

    标签: javascript arrays compare


    【解决方案1】:

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

    【讨论】:

    • 我认为的最佳答案。它不会修改数组,而且它比“创建数组,调用数组方法对于这个简单的操作来说太过分了”的答案要简洁得多
    • 这是大型数据集(30+ 列/100k 行)性能的绝对最佳答案。
    • 只是想知道,当 reduce 检查数组的第一个元素时,prev.Cost 是否未定义?还是从 0 开始?
    • 好点@Saheb。我刚刚进行了编辑,所以在这种情况下它将返回 null。
    • 我还对可能导致问题的其他输入添加了一些检查,例如非对象,或者某些对象中缺少该属性。最终,对于我认为的大多数情况,它会变得有点笨拙。
    【解决方案2】:

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

    (创建一个数组,调用数组方法对于这个简单的操作来说太过分了)。

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

    【讨论】:

    • 这是有道理的,我一直在考虑比较数组内的数据而不是外部高/低数字。
    • 我唯一要改变的是设置最低和最高有点多余。我宁愿少循环一次并设置lowest=highest=myArray[0],然后从1开始循环。
    • @32bitkid 好点。不过应该是myArray[0].Cost。但是,如果没有第一个元素,则会引发错误。因此,需要进行额外的检查,可能会抵消小的性能提升。
    • @Wilt 是的,维护另一个变量,当您找到最小值时更新,即var lowestObject; for (...)if (tmp &lt; lowest) { lowestObject = myArray[i]; lowest = tmp; }
    • 这个答案很老了,在 ECMAScript 2015 (ES6) 出来之前。当时是对的,但现在that answer 是更好的选择。
    【解决方案3】:

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

    myArray.sort(function (a, b) {
        return a.Cost - b.Cost
    })
    
    var min = myArray[0],
        max = myArray[myArray.length - 1]
    

    【讨论】:

    • 完整排序不是找到最小值/最大值的最快方法,但我想它会起作用。
    • 请注意,这将修改myArray,这可能不是预期的。
    • 对数组进行排序比遍历它要慢。排序复杂度:O(nlog(n)),遍历数组:O(n)
    【解决方案4】:

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

    【讨论】:

    • 在使用扩展运算符的 ES6 中,我们不再需要 apply。简单地说 - Math.min(...myArray.map(o =&gt; o.Cost)) 用于查找最小值,Math.max(...myArray.map(o =&gt; o.Cost)) 用于查找最大值。
    【解决方案5】:

    使用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() 所做的那样使用扩展运算符,我将不胜感激
    • 因为函数Math.max 接受多个参数而不是数组。扩展运算符会将数组转换为参数的“列表”。例如:Math.max(...[1,5,9]) 等价于 Math.max(1, 5, 9)。如果没有展开运算符,Math.max(myArray) 将返回 NaN(不是数字),因为该函数需要多个数字参数。我希望回复@NtshemboHlongwane 为时不晚;)
    【解决方案6】:

    我认为Rob W's answer 确实是正确的(+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; }));
    

    这些可以很容易地转换成更有用/更具体的形式。

    【讨论】:

    • 我已经对我们的解决方案进行了基准测试:jsperf.com/comparison-of-numbers。优化代码后(参见基准),两种方法的性能相似。没有优化,我的方法快了 14 倍。
    • @RoBW 哦,我完全希望您的版本能够方式更快,我只是提供了一种替代架构实现。 :)
    • @32bitkid 我的预期是一样的,但令人惊讶的是,该方法几乎与基准测试用例 3 中所见的一样快(优化后)。
    • @RobW 我同意,我没想到会这样。我很感兴趣。 :) 表明您应该始终进行基准测试而不是假设。
    • @RobW 不过要清楚一点,我认为随着浏览器结果的增多,您的实现将始终击败未优化和优化的版本。
    【解决方案7】:

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

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

    【讨论】:

    • 喜欢这个答案,如此紧凑且易于使用。
    【解决方案8】:

    为麦克斯

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

    最少

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

    【讨论】:

      【解决方案9】:

      这可以通过 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);
      &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"&gt;&lt;/script&gt;

      【讨论】:

        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

          这是更好的解决方案

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

          【讨论】:

            【解决方案12】:

            添加到 Tristan Reid 的答案(+ 使用 es6),您可以创建一个接受回调的函数,该函数将包含您要应用于 prevcurr 的运算符:

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

            【讨论】:

              【解决方案13】:

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

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

              演示:

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

              【讨论】:

                【解决方案14】:

                我们可以通过两种方法解决问题 上面已经解释了这两种方法,但是缺少性能测试,所以完成了那个

                1、原生java-script方式
                2、先排序对象然后很容易得到最小值 排序后的最大值

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

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

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

                【讨论】:

                  【解决方案15】:

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

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

                  【讨论】:

                    【解决方案16】:

                    您可以使用内置的 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
                    

                    【讨论】:

                      猜你喜欢
                      • 2018-10-30
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-02-16
                      • 1970-01-01
                      • 2014-05-10
                      • 1970-01-01
                      • 2020-11-06
                      • 1970-01-01
                      相关资源
                      最近更新 更多