Confronta array di oggetti JavaScript per ottenere min / max


95

Dispongo di una serie di oggetti e desidero confrontare tali oggetti su una specifica proprietà dell'oggetto. Ecco il mio array:

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

Vorrei concentrarmi specificamente sul "costo" e ottenere un valore minimo e massimo. Mi rendo conto che posso semplicemente prendere i valori di costo e inserirli in un array javascript e quindi eseguire il JavaScript veloce Max / Min .

Tuttavia, esiste un modo più semplice per farlo ignorando il passaggio dell'array al centro e disattivando direttamente le proprietà degli oggetti (in questo caso "Costo")?

Risposte:


53

Il modo più veloce, in questo caso, è scorrere tutti gli elementi e confrontarli con il valore più alto / più basso finora.

(Creare un array, invocare metodi di array è eccessivo per questa semplice operazione).

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

Questo ha senso, sono stato bloccato a pensare di confrontare i dati all'interno dell'array tra loro invece di un numero alto / basso esterno.
Firedrawndagger

1
L'unica cosa che cambierei è l'impostazione più bassa e la più alta è un po 'ridondante. Preferirei un loop in meno e impostare lowest=highest=myArray[0]e poi iniziare il loop a 1.
J. Holmes

1
@ 32bitkid Buon punto. dovrebbe essere myArray[0].Cost, però. Ma, se non c'è il primo elemento, verrà generato un errore. Quindi, è necessario un ulteriore controllo, possibilmente annullando il piccolo aumento delle prestazioni.
Rob W

Sono venuto qui perché voglio che l'oggetto stesso sia restituito. Non il valore più basso, il che è stato facile. Eventuali suggerimenti?
Wilt

1
@Wilt Sì, mantieni un'altra variabile che viene aggiornata quando hai trovato un valore più basso, cioè var lowestObject; for (...)eif (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Rob W

159

La riduzione è utile per cose come questa: eseguire operazioni aggregate (come min, max, avg, ecc.) Su un array di oggetti e restituire un singolo risultato:

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

... oppure puoi definire quella funzione interna con la sintassi della funzione ES6:

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

Se vuoi essere carino puoi allegarlo all'array:

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

Ora puoi semplicemente dire:

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

Tieni presente che se intendi utilizzarlo, non dispone di controlli completi per ogni situazione. Se passi un array di tipi primitivi, fallirà. Se controlli una proprietà che non esiste o se non tutti gli oggetti contengono quella proprietà, otterrai l'ultimo elemento. Questa versione è un po 'più ingombrante, ma ha questi controlli:

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
La migliore risposta secondo me. Non modifica l'array ed è molto più conciso della risposta che dice "Creare un array, invocare metodi di array è eccessivo per questa semplice operazione"
Julian Mann

Questa è la risposta migliore in assoluto per le prestazioni di set di dati di grandi dimensioni (oltre 30 colonne / 100.000 righe).
cerd

2
Mi chiedo solo, quando ridurre i controlli il primo elemento dell'array non prev.Costsarà indefinito? O inizia come 0?
GrayedFox

1
Buon punto @ Saheb. Ho appena modificato in modo che in quel caso restituirà null.
Tristan Reid

1
Ho anche aggiunto alcuni controlli per altri input che potrebbero causare problemi, come non oggetti o se quella proprietà mancava da alcuni oggetti. Alla fine, penso che stia diventando un po 'pesante per la maggior parte dei casi.
Tristan Reid

26

Usa sort, se non ti interessa che l'array venga modificato.

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

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

3
un ordinamento completo non è il modo più veloce per trovare min / max ma immagino che funzionerà.
J. Holmes

4
Basta essere consapevoli che questo cambierà myArray, il che potrebbe non essere previsto.
ziesemer

Ordinare un array è più lento che attraversarlo. Ordina complessità O(nlog(n))O(n)
:,

17

Usa le Mathfunzioni e estrai i valori che desideri map.

Ecco il 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

AGGIORNARE:

Se vuoi usare ES6:

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

14
In ES6 utilizzando Spread Operator, non abbiamo più bisogno di apply. Dì semplicemente: Math.min(...myArray.map(o => o.Cost))per trovare il minimo e Math.max(...myArray.map(o => o.Cost))per trovare il massimo.
Nitin

13

Credo che la risposta di Rob W è davvero quella giusta (+1), ma solo per divertimento: se si voleva essere "intelligente", si potrebbe fare qualcosa di simile:

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 se avessi una struttura profondamente nidificata, potresti diventare un po 'più funzionale e fare quanto segue:

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

Questi potrebbero essere facilmente trasformati in forme più utili / specifiche.


4
Ho confrontato le nostre soluzioni: jsperf.com/comparison-of-numbers . Dopo aver ottimizzato il codice (vedere il benchmark), le prestazioni di entrambi i metodi sono simili. Senza ottimizzazione, il mio metodo è 14 volte più veloce.
Rob W

2
@RoBW oh, mi sarei assolutamente aspettato che la tua versione fosse molto più veloce, stavo solo fornendo un'implementazione architettonica alternativa. :)
J. Holmes

@ 32bitkid mi aspettavo lo stesso, ma sorprendentemente, il metodo è quasi altrettanto veloce (dopo l'ottimizzazione), come si è visto nel caso di test 3 del benchmark.
Rob W

1
@ RobW sono d'accordo, non me lo sarei aspettato. Sono incuriosito. :) Questo dimostra che dovresti sempre fare il benchmark piuttosto che presumere.
J. Holmes

@ RobW Giusto per essere chiari, però, penso che con più risultati del browser, la tua implementazione batterebbe costantemente le versioni non ottimizzate e ottimizzate.
J. Holmes

6

per Max

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

per min

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

5

Prova ( aè un array, fè un campo da confrontare)

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
Adoro questa risposta, così compatta e facile da usare.
Dean

3

Utilizzando Array.prototype.reduce () , è possibile collegare funzioni di confronto per determinare l'elemento minimo, massimo, ecc. In un 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

Utilizzando 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

Questa è una soluzione migliore

    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

Aggiungendo alla risposta di Tristan Reid (+ usando es6), potresti creare una funzione che accetta un callback, che conterrà l'operatore che vuoi applicare a 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

Quindi potresti semplicemente chiamarlo usando:

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

2

Questo può essere ottenuto con le funzioni minBye di lodash maxBy.

Lodash minBye maxBydocumentazione

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

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

Questi metodi accettano un iteratee che viene invocato per ogni elemento in array per generare il criterio in base al quale il valore viene classificato. L'iterate viene invocato con un argomento: (valore).

Soluzione

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

possiamo risolvere il problema con due approcci entrambi i metodi sono già stati spiegati sopra ma mancava il test delle prestazioni completando quello

1, metodo java-script nativo
2, primo oggetto di ordinamento quindi è facile ottenere min max da obj ordinato

provo anche le prestazioni di entrambi gli approcci di traino

puoi anche eseguire e testare le prestazioni ... 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

Per una soluzione concisa e moderna, è possibile eseguire reduceun'operazione sull'array, tenendo traccia dei valori minimi e massimi correnti, quindi l'array viene iterato solo una volta (il che è ottimale).

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

Demo:


-2

Un altro, simile alla risposta di Kennebec, ma tutto in una riga:

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

-2

È possibile utilizzare l'oggetto Array integrato per utilizzare invece 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.