Comparez un tableau d'objets JavaScript pour obtenir un minimum / maximum

95

J'ai un tableau d'objets et je souhaite comparer ces objets sur une propriété d'objet spécifique. Voici mon tableau:

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

Je voudrais me concentrer sur le «coût» en particulier et obtenir une valeur minimale et maximale. Je me rends compte que je peux simplement saisir les valeurs de coût et les pousser dans un tableau javascript, puis exécuter Fast JavaScript Max / Min .

Cependant, y a-t-il un moyen plus simple de le faire en contournant l'étape de tableau au milieu et en supprimant directement les propriétés des objets (dans ce cas, "Coût")?

tiré
la source

Réponses:

53

Le moyen le plus rapide, dans ce cas, consiste à parcourir tous les éléments et à le comparer à la valeur la plus élevée / la plus basse, jusqu'à présent.

(Créer un tableau, invoquer des méthodes de tableau est excessif pour cette opération simple).

 // 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);
Rob W
la source
Cela a du sens, je suis resté coincé en pensant à comparer les données à l'intérieur du tableau les unes aux autres au lieu d'un nombre externe haut / bas.
firedrawndagger le
1
La seule chose que je changerais est de définir le plus bas et le plus élevé est un peu redondant. Je préférerais boucler une fois de moins et régler lowest=highest=myArray[0], puis démarrer la boucle à 1.
J. Holmes
1
@ 32bitkid Bon point. devrait être myArray[0].Cost, cependant. Mais, s'il n'y a pas de premier élément, une erreur sera générée. Une vérification supplémentaire est donc nécessaire, annulant éventuellement la petite amélioration des performances.
Rob W
Je suis venu ici parce que je veux que l'objet lui-même soit retourné. Pas la valeur la plus basse, ce qui était facile. Aucune suggestion?
Wilt
1
@Wilt Oui, maintenez une autre variable qui est mise à jour lorsque vous avez trouvé une valeur la plus basse, c'est var lowestObject; for (...)-à- dire etif (tmp < lowest) { lowestObject = myArray[i]; lowest = tmp; }
Rob W
163

La réduction est bonne pour des choses comme celle-ci: pour effectuer des opérations d'agrégation (comme min, max, avg, etc.) sur un tableau d'objets et renvoyer un seul résultat:

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

... ou vous pouvez définir cette fonction interne avec la syntaxe de la fonction ES6:

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

Si vous voulez être mignon, vous pouvez attacher ceci au tableau:

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

Maintenant, vous pouvez simplement dire:

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

Veuillez noter que si vous avez l'intention de l'utiliser, il n'y a pas de contrôles complets pour chaque situation. Si vous passez un tableau de types primitifs, cela échouera. Si vous recherchez une propriété qui n'existe pas, ou si tous les objets ne contiennent pas cette propriété, vous obtiendrez le dernier élément. Cette version est un peu plus volumineuse, mais a ces vérifications:

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;
 }
Tristan Reid
la source
14
Meilleure réponse à mon avis. Il ne modifie pas le tableau et il est beaucoup plus concis que la réponse qui dit "Créer un tableau, invoquer des méthodes de tableau est exagéré pour cette opération simple"
Julian Mann
C'est la meilleure réponse absolue pour les performances des grands ensembles de données (plus de 30 colonnes / 100 000 lignes).
cerd
2
Vous vous demandez simplement, lorsque réduire les vérifications, le premier élément du tableau ne sera pas prev.Costindéfini? Ou est-ce qu'il commence à 0?
GrayedFox
1
Bon point @Saheb. Je viens de modifier donc il retournera null dans ce cas.
Tristan Reid
1
J'ai également ajouté des vérifications pour d'autres entrées qui pourraient causer des problèmes, comme des non-objets, ou si cette propriété était absente de certains objets. En fin de compte, je pense que cela devient un peu lourd dans la plupart des cas.
Tristan Reid
26

Utilisez sort, si vous ne vous souciez pas du tableau en cours de modification.

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

var min = myArray[0],
    max = myArray[myArray.length - 1]
katspaugh
la source
3
un tri complet n'est pas le moyen le plus rapide de trouver min / max, mais je suppose que cela fonctionnera.
J. Holmes
4
Sachez simplement que cela va modifier myArray, ce qui n'est peut-être pas prévu.
ziesemer le
Le tri d'un tableau est plus lent que son parcours. Complexité du tri O(nlog(n))O(n)
:,
18

Utilisez des Mathfonctions et extrayez les valeurs souhaitées map.

Voici le 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

METTRE À JOUR:

Si vous souhaitez utiliser ES6:

var min = Math.min.apply(null, myArray.map(item => item.Cost)),
    max = Math.max.apply(null, myArray.map(item => item.Cost));
Rupert
la source
15
Dans ES6 utilisant Spread Operator, nous n'avons plus besoin apply. Dites simplement - Math.min(...myArray.map(o => o.Cost))pour trouver le minimum et Math.max(...myArray.map(o => o.Cost))pour trouver le maximum.
Nitin le
13

Je pense que la réponse de Rob W est vraiment la bonne (+1), mais juste pour le plaisir: si vous vouliez être "intelligent", vous pourriez faire quelque chose comme ceci:

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 si vous aviez une structure profondément imbriquée, vous pourriez devenir un peu plus fonctionnel et faire ce qui suit:

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

Ceux-ci pourraient facilement être curry dans des formes plus utiles / spécifiques.

J. Holmes
la source
4
J'ai comparé nos solutions: jsperf.com/comparison-of-numbers . Après avoir optimisé votre code (voir le benchmark), les performances des deux méthodes sont similaires. Sans optimisation, ma méthode est 14 fois plus rapide.
Rob W
2
@RoBW oh je m'attendrais totalement à ce que votre version soit bien plus rapide, je fournissais juste une implémentation architecturale alternative. :)
J. Holmes
@ 32bitkid Je m'attendais à la même chose, mais étonnamment, la méthode est presque aussi rapide (après optimisation), comme on le voit dans le cas de test 3 du benchmark.
Rob W
1
@RobW Je suis d'accord, je ne m'attendais pas à ça. Je suis intrigué. :) Cela montre que vous devriez toujours comparer plutôt que supposer.
J. Holmes
@RobW Pour être clair cependant, je pense qu'avec plus de résultats de navigateur, votre implémentation battrait systématiquement les versions non optimisées et optimisées.
J. Holmes
6

pour Max

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

pendant min

Math.min.apply(Math, myArray.map(a => a.Cost));
Issa Lafi
la source
5

Try ( aest un tableau, fest un champ à comparer)

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

Kamil Kiełczewski
la source
2
J'adore cette réponse, si compacte et facile à utiliser.
Dean
3

En utilisant Array.prototype.reduce () , vous pouvez connecter des fonctions de comparaison pour déterminer l'élément min, max, etc. dans un tableau.

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;

M. Polywhirl
la source
3

Utilisation Math.minet 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);

JuZDePeche
la source
2

C'est une meilleure solution

    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);
Deepak Sisodiya
la source
2

En ajoutant à la réponse de Tristan Reid (+ en utilisant es6), vous pouvez créer une fonction qui accepte un rappel, qui contiendra l'opérateur que vous souhaitez appliquer au prevet 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

Ensuite, vous pouvez simplement l'appeler en utilisant:

const costMin = compare(myArray, 'Cost', (a, b) => a < b);
const costMax = compare(myArray, 'Cost', (a, b) => a > b);
James Moran
la source
2

Ceci peut être réalisé avec des lodash minByet des maxByfonctions.

Lodash minByet maxBydocumentation

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

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

Ces méthodes acceptent un iteratee qui est appelé pour chaque élément du tableau pour générer le critère selon lequel la valeur est classée. L'itéré est invoqué avec un argument: (valeur).

Solution

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>

Trent
la source
0

nous pouvons résoudre le problème par deux approches, les deux méthodes sont déjà expliquées ci-dessus mais le test de performance manquait donc en complétant celui-là

1, mode de script Java natif
2, triez d'abord l'objet, puis il est facile d'obtenir min max à partir d'obj trié

Je teste également les performances des deux approches de remorquage

vous pouvez également exécuter et tester les performances ... Bon codage (:

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

Jadli
la source
0

Pour une solution concise et moderne, on peut effectuer une reduceopération sur le tableau, en gardant une trace des valeurs minimales et maximales actuelles, de sorte que le tableau ne soit itéré qu'une seule fois (ce qui est optimal).

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

Démo:

iota
la source
-2

Un autre, similaire à la réponse de Kennebec, mais le tout sur une seule ligne:

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

Vous pouvez utiliser l'objet Array intégré pour utiliser Math.max / Math.min à la place:

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
code source
la source