Compare la matriz de objetos de JavaScript para obtener mín. / Máx.


95

Tengo una matriz de objetos y quiero comparar esos objetos en una propiedad de objeto específica. Aquí está mi matriz:

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

Me gustaría concentrarme en el "costo" específicamente y obtener un valor mínimo y máximo. Me doy cuenta de que puedo tomar los valores de costo y enviarlos a una matriz de JavaScript y luego ejecutar Fast JavaScript Max / Min .

Sin embargo, ¿hay una manera más fácil de hacer esto omitiendo el paso de la matriz en el medio y saliendo directamente de las propiedades de los objetos (en este caso, "Costo")?

Respuestas:


53

La forma más rápida, en este caso, es recorrer todos los elementos y compararlos con el valor más alto / más bajo, hasta ahora.

(Crear una matriz, invocar métodos de matriz es excesivo para esta simple operación).

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

Esto tiene sentido, me he quedado atascado pensando en comparar datos dentro de la matriz entre sí en lugar de un número externo alto / bajo.
firedrawndagger

1
Lo único que cambiaría es la configuración más baja y la más alta es un poco redundante. Preferiría hacer un bucle una vez menos y configurarlo lowest=highest=myArray[0]y luego comenzar el bucle en 1.
J. Holmes

1
@ 32bitkid Buen punto. debería ser myArray[0].Cost, sin embargo. Pero, si no hay un primer elemento, se producirá un error. Por lo tanto, se necesita una verificación adicional, posiblemente deshaciendo el pequeño aumento de rendimiento.
Rob W

Vine aquí porque quiero que se devuelva el objeto. No es el valor más bajo, lo cual fue fácil. ¿Alguna sugerencia?
March

1
@Wilt Sí, mantenga otra variable que se actualice cuando haya encontrado un valor más bajo, es decir, var lowestObject; for (...)yif (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Rob W

163

La reducción es buena para cosas como esta: para realizar operaciones agregadas (como min, max, avg, etc.) en una matriz de objetos y devolver un solo resultado:

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

... o puede definir esa función interna con la sintaxis de la función ES6:

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

Si quieres ser lindo, puedes adjuntar esto a la matriz:

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

Ahora solo puedes decir:

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

Tenga en cuenta que si tiene la intención de usar esto, no tiene controles completos para cada situación. Si pasa una matriz de tipos primitivos, fallará. Si busca una propiedad que no existe, o si no todos los objetos contienen esa propiedad, obtendrá el último elemento. Esta versión es un poco más voluminosa, pero tiene esos controles:

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

14
La mejor respuesta en mi opinión. No modifica la matriz y es mucho más concisa que la respuesta que dice "Crear una matriz, invocar métodos de matriz es excesivo para esta simple operación"
Julian Mann

Esta es la mejor respuesta absoluta para el rendimiento de grandes conjuntos de datos (más de 30 columnas / 100.000 filas).
cerd

2
Me pregunto, cuando reduce las comprobaciones, el primer elemento del arreglo no prev.Costestará indefinido ¿O se inicia como 0?
GrayedFox

1
Buen punto @Saheb. Acabo de editar, por lo que devolverá nulo en ese caso.
Tristan Reid

1
También agregué algunas verificaciones para otras entradas que podrían causar problemas, como no objetos, o si esa propiedad faltaba en algunos de los objetos. En última instancia, creo que se está volviendo un poco torpe en la mayoría de los casos.
Tristan Reid

26

Úselo sort, si no le importa que se modifique la matriz.

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

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

3
una clasificación completa no es la forma más rápida de encontrar min / max, pero supongo que funcionará.
J. Holmes

4
Solo tenga en cuenta que esto modificará myArray, lo que puede no ser esperado.
ziesemer

Ordenar una matriz es más lento que atravesarla. Ordenar complejidad : O(nlog(n)), atravesando una matriz:O(n)
Mourad Qqch

18

Utilice Mathfunciones y extraiga los valores que desee map.

Aquí está el 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

ACTUALIZAR:

Si desea utilizar ES6:

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

15
En ES6 usando Spread Operator, ya no necesitamos apply. Simplemente diga: Math.min(...myArray.map(o => o.Cost))para encontrar el mínimo y Math.max(...myArray.map(o => o.Cost))para encontrar el máximo.
Nitin

13

Creo que la respuesta de Rob W es realmente la correcta (+1), pero solo por diversión: si quisieras ser "inteligente", podrías hacer algo como esto:

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

o si tuviera una estructura profundamente anidada, podría volverse un poco más funcional y hacer lo siguiente:

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

Estos podrían currizarse fácilmente en formas más útiles / específicas.


4
He comparado nuestras soluciones: jsperf.com/comparison-of-numbers . Después de optimizar su código (consulte el punto de referencia), el rendimiento de ambos métodos es similar. Sin optimización, mi método es 14 veces más rápido.
Rob W

2
@RoBW oh, esperaría que tu versión fuera mucho más rápida, solo estaba proporcionando una implementación arquitectónica alternativa. :)
J. Holmes

@ 32bitkid Esperaba lo mismo, pero sorprendentemente, el método es casi tan rápido (después de la optimización), como se ve en el caso de prueba 3 del punto de referencia.
Rob W

1
@RobW estoy de acuerdo, no hubiera esperado eso. Estoy intrigado. :) Va a demostrar que siempre se debe comparar en lugar de asumir.
J. Holmes

@RobW Sin embargo, para ser claro, creo que con más resultados del navegador, su implementación superaría consistentemente a las versiones optimizadas y no optimizadas.
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

Prueba ( aes una matriz, fes un 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
Me encanta esta respuesta, tan compacta y fácil de usar.
Dean

3

Usando Array.prototype.reduce () , puede conectar funciones de comparación para determinar el elemento mínimo, máximo, etc. en una matriz.

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.miny 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 es una mejor solución

    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

Agregando a la respuesta de Tristan Reid (+ usando es6), puede crear una función que acepte una devolución de llamada, que contendrá el operador que desea que se aplique al prevy 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

Entonces podrías simplemente llamarlo usando:

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

2

Esto se puede lograr con lodash minByymaxBy funciones .

Lodash minByy maxBydocumentación

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

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

Estos métodos aceptan un iteratee que se invoca para cada elemento en la matriz para generar el criterio por el cual se clasifica el valor. El iteratee se invoca con un argumento: (valor).

Solución

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 el problema mediante dos enfoques, ambos métodos ya se explicaron anteriormente, pero faltaba la prueba de rendimiento, así que completando ese

1, forma nativa de script java
2, primero ordenar el objeto y luego obtener el mínimo máximo del objeto ordenado

También pruebo el rendimiento de ambos enfoques de remolque

también puede ejecutar y probar el rendimiento ... Happy coding (:

//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 una solución concisa y moderna, se puede realizar una reduceoperación sobre la matriz, haciendo un seguimiento de los valores mínimos y máximos actuales, por lo que la matriz solo se repite una vez (lo cual es óptimo).

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

Manifestación:


-2

Otro, similar a la respuesta de Kennebec, pero todo en una línea:

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

-2

Puede usar el objeto Array incorporado para usar Math.max / Math.min en su lugar:

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.