Soulignant: sortBy () basé sur plusieurs attributs

J'essaie de trier un tableau avec des objets basés sur plusieurs attributs. C'est-à-dire si le premier attribut est identique entre deux objets, un second attribut doit être utilisé pour comapare les deux objets. Par exemple, considérez le tableau suivant:

var patients = [ [{name: 'John', roomNumber: 1, bedNumber: 1}], [{name: 'Lisa', roomNumber: 1, bedNumber: 2}], [{name: 'Chris', roomNumber: 2, bedNumber: 1}], [{name: 'Omar', roomNumber: 3, bedNumber: 1}] ]; 

En roomNumber par l'attribut roomNumber j'utiliserais le code suivant:

 var sortedArray = _.sortBy(patients, function(patient) { return patient[0].roomNumber; }); 

Cela fonctionne bien, mais comment procéder pour que 'John' et 'Lisa' soient triés correctement?

sortBy dit qu'il s'agit d'un algorithme de tri stable afin que vous puissiez d'abord trier votre deuxième propriété, puis trier par votre première propriété, comme ceci:

 var sortedArray = _(patients).chain().sortBy(function(patient) { return patient[0].name; }).sortBy(function(patient) { return patient[1].roomNumber; }).value(); 

Quand le deuxième sortBy trouve que John et Lisa ont le même numéro de pièce, ils les garderont dans l'ordre où ils les ont trouvés, le premier sortBy réglé sur "Lisa, John".

Voici un truc de piratage que j'utilise parfois dans ces cas: combinez les propriétés de telle sorte que le résultat sera sorti:

 var sortedArray = _.sortBy(patients, function(patient) { return [patient[0].roomNumber, patient[0].name].join("_"); }); 

Cependant, comme je l'ai dit, c'est très difficile. Pour ce faire, vous devriez probablement utiliser la méthode de sort JavaScript principale :

 patients.sort(function(x, y) { var roomX = x[0].roomNumber; var roomY = y[0].roomNumber; if (roomX !== roomY) { return compare(roomX, roomY); } return compare(x[0].name, y[0].name); }); // General comparison function for convenience function compare(x, y) { if (x === y) { return 0; } return x > y ? 1 : -1; } 

Bien sûr, cela va trier votre tableau en place. Si vous voulez une copie triée (comme _.sortBy vous l' _.sortBy ), cloner le tableau d'abord:

 function sortOutOfPlace(sequence, sorter) { var copy = _.clone(sequence); copy.sort(sorter); return copy; } 

Par ennui, je viens d'écrire une solution générale (pour trier par n'importe quel nombre arbitraire de clés) pour cela aussi: jetez un oeil .

Je sais que je suis en retard à la fête, mais je voulais ajouter cela pour ceux qui ont besoin d'une solution propre et rapide qui sont déjà proposés. Vous pouvez chaîner les appels de tri par ordre de propriété la moins importante de la propriété la plus importante. Dans le code ci-dessous, je crée un nouveau groupe de patients triés par Nom dans RoomNumber à partir du groupe d'origine appelé patients .

 var sortedPatients = _.chain(patients) .sortBy('Name') .sortBy('RoomNumber') .value(); 

Votre démarrage pour les patients est-il un peu bizarre, n'est-ce pas? Pourquoi ne pas initialiser cette variable comme ceci, comme un véritable ensemble d'objets, vous pouvez le faire en utilisant _.flatten () et non comme un tableau de tableaux d'un seul objet, peut-être un problème de frappe):

 var patients = [ {name: 'Omar', roomNumber: 3, bedNumber: 1}, {name: 'John', roomNumber: 1, bedNumber: 1}, {name: 'Chris', roomNumber: 2, bedNumber: 1}, {name: 'Lisa', roomNumber: 1, bedNumber: 2}, {name: 'Kiko', roomNumber: 1, bedNumber: 2} ]; 

J'ai trié la liste différemment et ajoute Kiko dans le lit de Lisa; Juste pour s'amuser et voir quels changements seraient réalisés …

 var sorted = _(patients).sortBy( function(patient){ return [patient.roomNumber, patient.bedNumber, patient.name]; }); 

Inspectez-les et vous verrez cela

 [ {bedNumber: 1, name: "John", roomNumber: 1}, {bedNumber: 2, name: "Kiko", roomNumber: 1}, {bedNumber: 2, name: "Lisa", roomNumber: 1}, {bedNumber: 1, name: "Chris", roomNumber: 2}, {bedNumber: 1, name: "Omar", roomNumber: 3} ] 

Alors ma réponse est: utilisez un tableau dans votre fonction de rappel, cela ressemble beaucoup à la réponse de Dan Tao , j'oublie simplement la jointure (peut-être parce que j'ai supprimé le tableau de tableaux d'un élément unique :))
En utilisant votre structure de données, ce serait:

 var sorted = _(patients).chain() .flatten() .sortBy( function(patient){ return [patient.roomNumber, patient.bedNumber, patient.name]; }) .value(); 

Et une charge de test serait intéressante …

Aucune de ces réponses n'est idéale comme méthode générale pour l'utilisation de plusieurs champs dans une sorte. Toutes les approches ci-dessus sont inefficaces car elles nécessitent le tri du tableau à plusieurs reprises (ce qui, sur une liste assez large, pourrait ralentir beaucoup les choses) ou ils génèrent énormément d'objets d'ordures que la machine virtuelle devra nettoyer (et finalement ralentir Le programme est en panne).

Voici une solution rapide, efficace, qui permet facilement le tri en sens inverse, et peut être utilisée avec le underscore de underscore ou le lodash , ou directement avec Array.sort

La partie la plus importante est la méthode compositeComparator , qui prend un tableau de fonctions de comparateur et renvoie une nouvelle fonction de comparateur composite.

 /** * Chains a comparator function to another comparator * and returns the result of the first comparator, unless * the first comparator returns 0, in which case the * result of the second comparator is used. */ function makeChainedComparator(first, next) { return function(a, b) { var result = first(a, b); if (result !== 0) return result; return next(a, b); } } /** * Given an array of comparators, returns a new comparator with * descending priority such that * the next comparator will only be used if the precending on returned * 0 (ie, found the two objects to be equal) * * Allows multiple sorts to be used simply. For example, * sort by column a, then sort by column b, then sort by column c */ function compositeComparator(comparators) { return comparators.reduceRight(function(memo, comparator) { return makeChainedComparator(comparator, memo); }); } 

Vous aurez également besoin d'une fonction de comparaison pour comparer les champs que vous souhaitez trier. La fonction naturalSort créera un comparateur donné dans un champ particulier. L'écriture d'un comparateur pour le tri en sens inverse est aussi trivial.

 function naturalSort(field) { return function(a, b) { var c1 = a[field]; var c2 = b[field]; if (c1 > c2) return 1; if (c1 < c2) return -1; return 0; } } 

(Tout le code jusqu'à présent est réutilisable et pourrait être conservé dans le module d'utilité, par exemple)

Ensuite, vous devez créer le comparateur composite. Pour notre exemple, il ressemblerait à ceci:

 var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]); 

Cela va trier par numéro de pièce, suivi du nom. L'ajout de critères de tri supplémentaires est trivial et n'affecte pas les performances du tri.

 var patients = [ {name: 'John', roomNumber: 3, bedNumber: 1}, {name: 'Omar', roomNumber: 2, bedNumber: 1}, {name: 'Lisa', roomNumber: 2, bedNumber: 2}, {name: 'Chris', roomNumber: 1, bedNumber: 1}, ]; // Sort using the composite patients.sort(cmp); console.log(patients); 

Renvoie le suivant

 [ { name: 'Chris', roomNumber: 1, bedNumber: 1 }, { name: 'Lisa', roomNumber: 2, bedNumber: 2 }, { name: 'Omar', roomNumber: 2, bedNumber: 1 }, { name: 'John', roomNumber: 3, bedNumber: 1 } ] 

La raison pour laquelle je préfère cette méthode, c'est qu'il permet un tri rapide sur un nombre arbitraire de champs, ne génère pas beaucoup de déchets ou effectue une concaténation de chaîne dans le type et peut facilement être utilisé afin que certaines colonnes soient triées en même temps que les colonnes d'ordre utilisent des effets naturels Trier.

Vous pouvez concaténer les propriétés que vous souhaitez trier dans l'itérateur:

 return [patient[0].roomNumber,patient[0].name].join('|'); 

Ou quelque chose d'équivalent.

REMARQUE: Puisque vous convertissez l'attribut numérique de la pièce N ° à une chaîne, vous devriez faire quelque chose si vous aviez des numéros de pièce> 10. Sinon, 11 viendront avant 2. Vous pouvez cocher avec des zéros de tête pour résoudre le problème, c'est-à-dire 01 au lieu de 1.

Je pense que vous feriez mieux d'utiliser _.orderBy au lieu de sortBy :

 _.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])