Compare a matriz de objetos JavaScript para obter mín. / Máx.


95

Eu tenho uma matriz de objetos e quero comparar esses objetos em uma propriedade de objeto específica. Aqui está minha matriz:

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

Gostaria de me concentrar no "custo" especificamente e obter um valor mínimo e máximo. Percebo que posso simplesmente pegar os valores de custo e colocá-los em uma matriz javascript e, em seguida, executar o Fast JavaScript Max / Min .

No entanto, existe uma maneira mais fácil de fazer isso ignorando a etapa do array no meio e saindo das propriedades dos objetos (neste caso, "Custo") diretamente?

Respostas:


53

A maneira mais rápida, neste caso, é percorrer todos os elementos e compará-lo com o valor mais alto / mais baixo, até agora.

(Criar um array, invocar métodos de array é um exagero para esta operação simples).

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

Isso faz sentido, estou pensando em comparar os dados dentro da matriz entre si, em vez de um número externo alto / baixo.
firedrawndagger de

1
A única coisa que eu mudaria é definir o mínimo e o máximo é um pouco redundante. Prefiro fazer um loop menos uma vez e definir lowest=highest=myArray[0]e, em seguida, iniciar o loop em 1.
J. Holmes

1
@ 32bitkid Bom ponto. deveria ser myArray[0].Cost, no entanto. Mas, se não houver um primeiro elemento, um erro será gerado. Portanto, uma verificação adicional é necessária, possivelmente desfazendo o pequeno aumento de desempenho.
Rob W

Vim aqui porque quero que o próprio objeto seja devolvido. Não é o valor mais baixo, o que foi fácil. Alguma sugestão?
Wilt

1
@Wilt Sim, mantenha outra variável que é atualizada quando você encontra um valor mais baixo, ou seja, var lowestObject; for (...)eif (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Rob W

159

A redução é boa para coisas como esta: realizar operações agregadas (como min, max, avg etc.) em uma matriz de objetos e retornar um único resultado:

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

... ou você pode definir essa função interna com a sintaxe da função ES6:

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

Se você quiser ser fofo, pode anexar isto ao array:

Array.prototype.hasMin = function(attrib) {
    return (this.length && this.reduce(function(prev, curr){ 
        return prev[attrib] < curr[attrib] ? prev : curr; 
    })) || null;
 }

Agora você pode apenas dizer:

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

Observe que, se você pretende usar isso, não há verificações completas para todas as situações. Se você passar uma matriz de tipos primitivos, ela falhará. Se você verificar uma propriedade que não existe, ou se nem todos os objetos contiverem essa propriedade, você obterá o último elemento. Esta versão é um pouco mais volumosa, mas tem as seguintes verificações:

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
Melhor resposta na minha opinião. Ele não modifica a matriz e é muito mais conciso do que a resposta que diz "Criar uma matriz, invocar métodos de matriz é um exagero para esta operação simples"
Julian Mann,

Esta é a melhor resposta absoluta para o desempenho de grandes conjuntos de dados (mais de 30 colunas / 100 mil linhas).
cerd

2
Apenas imaginando, quando reduzir verifica o primeiro elemento da matriz não prev.Costserá indefinido? Ou inicia como 0?
GrayedFox

1
Boa observação @Saheb. Acabei de editar, então ele retornará null nesse caso.
Tristan Reid

1
Também adicionei algumas verificações para outras entradas que podem causar problemas, como não-objetos, ou se essa propriedade estava faltando em alguns dos objetos. Em última análise, acho que está ficando um pouco pesado para a maioria dos casos.
Tristan Reid

26

Use sort, se você não se importar com a modificação do array.

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

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

3
uma classificação completa não é a maneira mais rápida de encontrar mín. / máx., mas acho que funcionará.
J. Holmes

4
Esteja ciente de que isso irá modificar myArray, o que pode não ser esperado.
ziesemer

Classificar uma matriz é mais lento do que percorrê-la. Complexidade da classificação O(nlog(n))O(n)
:,

17

Use Mathfunções e extraia os valores desejados map.

Aqui está o 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

ATUALIZAR:

Se você deseja usar ES6:

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

14
No ES6 usando Spread Operator, não precisamos mais apply. Basta dizer - Math.min(...myArray.map(o => o.Cost))para encontrar o mínimo e Math.max(...myArray.map(o => o.Cost))para encontrar o máximo.
Nitin

13

Acho que a resposta de Rob W é realmente a certa (+1), mas apenas por diversão: se você quisesse ser "inteligente", poderia fazer algo assim:

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

ou se você tivesse uma estrutura profundamente aninhada, você poderia ficar um pouco mais funcional e fazer o seguinte:

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

Isso poderia ser facilmente transformado em formas mais úteis / específicas.


4
Avaliei nossas soluções: jsperf.com/comparison-of-numbers . Depois de otimizar seu código (consulte o benchmark), o desempenho de ambos os métodos é semelhante. Sem otimização, meu método é 14 vezes mais rápido.
Rob W

2
@RoBW oh, eu esperaria totalmente que sua versão fosse muito mais rápida, eu estava apenas fornecendo uma implementação de arquitetura alternativa. :)
J. Holmes

@ 32bitkid Eu esperava o mesmo, mas, surpreendentemente, o método é quase tão rápido (após a otimização), como visto no caso de teste 3 do benchmark.
Rob W

1
@RobW concordo, não esperava isso. Estou intrigado. :) Isso mostra que você deve sempre avaliar em vez de assumir.
J. Holmes

@RobW Só para ficar claro, eu acho que com mais resultados do navegador, sua implementação consistentemente superaria as versões otimizadas e não otimizadas.
J. Holmes

6

para Max

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

por min

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

5

Experimente ( aé uma matriz, fé um campo para comparar)

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
Adorei essa resposta, tão compacta e fácil de usar.
Dean,

3

Usando Array.prototype.reduce () , você pode inserir funções de comparador para determinar o item min, max, etc. em um 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;


3

Usando Math.mine 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);


2

Esta é uma solução mais melhor

    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

Adicionando à resposta de Tristan Reid (+ usando es6), você poderia criar uma função que aceita um retorno de chamada, que conterá o operador que você deseja aplicar ao preve 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

Então você poderia simplesmente chamá-lo usando:

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

2

Isso pode ser alcançado com lodash minBye maxByfunções.

Lodash's minBye maxBydocumentação

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

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

Esses métodos aceitam um iteratário que é chamado para cada elemento da matriz para gerar o critério pelo qual o valor é classificado. O iteratário é invocado com um argumento: (valor).

Solução

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

podemos resolver o problema por duas abordagens, ambos os métodos já foram explicados acima, mas o teste de desempenho estava faltando, portanto, completar aquele

1, modo
2 de java-script nativo , primeiro classifique o objeto e, em seguida, é fácil obter min max do obj classificado

Eu também testo o desempenho de ambas as abordagens

você também pode executar e testar o desempenho ... Boa codificação (:

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

Para uma solução concisa e moderna, pode-se executar uma reduceoperação sobre o array, mantendo o controle dos valores mínimos e máximos atuais, de modo que o array seja iterado apenas uma vez (o que é ótimo).

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

Demo:


-2

Outro, semelhante à resposta de Kennebec, mas tudo em uma linha:

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

-2

Você pode usar o objeto Array integrado para usar Math.max / Math.min em vez disso:

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.