Trier le tableau d'objets par valeur de propriété de chaîne

2764

J'ai un tableau d'objets JavaScript:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

Comment puis-je les trier par la valeur de last_nomen JavaScript?

Je sais sort(a,b), mais cela ne semble fonctionner que sur les chaînes et les nombres. Dois-je ajouter une toString()méthode à mes objets?

Tyrone Slothrop
la source
7
Ce script vous permet de faire exactement cela, sauf si vous voulez écrire votre propre fonction de comparaison ou trieur: thomasfrank.se/sorting_things.html
Baversjo
le moyen le plus rapide consiste à utiliser le module de tableau de tri isomorphe qui fonctionne nativement dans le navigateur et le nœud, prenant en charge tout type d'entrée, les champs calculés et les ordres de tri personnalisés.
Lloyd

Réponses:

3923

Il est assez facile d'écrire votre propre fonction de comparaison:

function compare( a, b ) {
  if ( a.last_nom < b.last_nom ){
    return -1;
  }
  if ( a.last_nom > b.last_nom ){
    return 1;
  }
  return 0;
}

objs.sort( compare );

Ou en ligne (c / o Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0)); 
Wogan
la source
441
Ou en ligne: objs.sort (fonction (a, b) {return (a.last_nom> b.last_nom)? 1: ((b.last_nom> a.last_nom)? -1: 0);});
Marco Demaio
35
Documents
176
return a.last_nom.localeCompare(b.last_nom)fonctionnera aussi.
Cerbrus
146
pour ceux qui recherchent un tri où le champ est numérique, le corps de la fonction de comparaison: return a.value - b.value;(ASC)
Andre Figueiredo
20
@Cerbrus localeCompareest important lors de l'utilisation de caractères accentués dans des langues étrangères, et plus élégant également.
Marcos Lima
839

Vous pouvez également créer une fonction de tri dynamique qui trie les objets en fonction de leur valeur que vous transmettez:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        /* next line works with strings and numbers, 
         * and you may want to customize it to your needs
         */
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

Vous pouvez donc avoir un tableau d'objets comme celui-ci:

var People = [
    {Name: "Name", Surname: "Surname"},
    {Name:"AAA", Surname:"ZZZ"},
    {Name: "Name", Surname: "AAA"}
];

... et cela fonctionnera lorsque vous:

People.sort(dynamicSort("Name"));
People.sort(dynamicSort("Surname"));
People.sort(dynamicSort("-Surname"));

En fait, cela répond déjà à la question. La partie ci-dessous est écrite parce que de nombreuses personnes m'ont contacté, se plaignant du fait que cela ne fonctionne pas avec plusieurs paramètres .

Paramètres multiples

Vous pouvez utiliser la fonction ci-dessous pour générer des fonctions de tri avec plusieurs paramètres de tri.

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

Ce qui vous permettrait de faire quelque chose comme ça:

People.sort(dynamicSortMultiple("Name", "-Surname"));

Tableau de sous-classement

Pour les plus chanceux d'entre nous qui peuvent utiliser ES6, ce qui permet d'étendre les objets natifs:

class MyArray extends Array {
    sortBy(...args) {
        return this.sort(dynamicSortMultiple.apply(null, args));
    }
}

Cela permettrait ceci:

MyArray.from(People).sortBy("Name", "-Surname");
Ege Özcan
la source
Veuillez noter que les noms de propriété en JavaScript peuvent être n'importe quelle chaîne et si vous avez des propriétés commençant par un "-" (extrêmement improbable et probablement pas une bonne idée), vous devrez modifier la fonction dynamicSort pour utiliser autre chose comme tri inverse indicateur.
Ege Özcan
J'ai remarqué que l' dynamicSort()exemple ci-dessus placera les majuscules avant les minuscules. Par exemple, si je les valeurs APd, Aklin, et Abe- les résultats dans un ASC devrait être une sorte Abe, Aklin, APd. Mais avec votre exemple, les résultats sont APd, Abe, Aklin. Quoi qu'il en soit pour corriger ce comportement?
Lloyd Banks
6
@LloydBanks si vous l'utilisez strictement pour les chaînes, vous pouvez utiliser à la var result = a[property].localeCompare(b[property]);place de var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;.
Ege Özcan
1
@ EgeÖzcan Il devrait soit mettre des éléments en haut ou en bas, voici l'implémentation que j'ai fini par utiliser pastebin.com/g4dt23jU
dzh
1
C'est une excellente solution, mais vous avez un problème si vous comparez les chiffres. Veuillez ajouter ceci avant le contrôle:if( !isNaN(a[property]) ) a[property] = Number(a[property]); if( !isNaN(b[property]) ) b[property] = Number(b[property]);
Ivijan Stefan Stipić
417

Dans ES6 / ES2015 ou version ultérieure, vous pouvez procéder comme suit:

objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom));

Avant ES6 / ES2015

objs.sort(function(a, b) {
    return a.last_nom.localeCompare(b.last_nom)
});
Vlad Bezden
la source
29
cela est disponible depuis JS 1.1, la grosse flèche est la pièce ES6 / 2015. Mais toujours très utile, et meilleure réponse à mon avis
Jon Harding
13
@PratikKelwalkar: si vous avez besoin d'inverser, changez simplement la comparaison a et b: objs.sort ((a, b) => b.last_nom.localeCompare (a.last_nom));
Vlad Bezden
est-il possible d'utiliser également un index pour adresser le champ de tri: au lieu d' last_nomutiliser uniquement le nombre dans le tableau 1:?
and-bri
4
@VladBezden merci pour votre réponse! Cette solution est la première avec un effort de programmation très faible et des résultats de tri corrects et un tableau de chaînes comme: ["Name1", "Name10", "Name2", "autre chose", "Name11"]. J'ai fait le tri pour travailler correctement avecobjs.sort((a, b) => a.last_nom.localeCompare(b.last_nom, undefined, {numberic: true}));
scipper
1
Pour ce faire avec des nombres décroissants: .sort ((a, b) => b.numberProperty - a.numberProperty). Ascendant: .sort ((a, b) => a.numberProperty - b.numberProperty)
Sebastian Patten
188

underscore.js

utilisez le soulignement, c'est petit et génial ...

sortBy_.sortBy (liste, itérateur, [contexte]) Renvoie une copie triée de la liste, classée par ordre croissant par les résultats de l'exécution de chaque valeur via l'itérateur. Itérateur peut également être le nom de chaîne de la propriété à trier (par exemple, la longueur).

var objs = [ 
  { first_nom: 'Lazslo',last_nom: 'Jamf' },
  { first_nom: 'Pig', last_nom: 'Bodine'  },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

var sortedObjs = _.sortBy( objs, 'first_nom' );
David Morrow
la source
18
David, pourriez - vous modifier la réponse à dire var sortedObjs = _.sortBy( objs, 'first_nom' );. neobjs sera pas trié lui-même en conséquence. La fonction renverra un tableau trié. Cela le rendrait plus explicite.
Jess
10
Pour inverser le tri:var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
Erdal G.
2
vous devez charger la librairie javascript "underscore":<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"> </script>
and-bri
5
Également disponible Lodashpour ceux qui préfèrent celui-ci
WoJ
2
En lodash, ce serait la même chose: var sortedObjs = _.sortBy( objs, 'first_nom' );ou si vous le souhaitez dans un ordre différent:var sortedObjs = _.orderBy( objs, ['first_nom'],['dsc'] );
Travis Heeter
186

Ne comprenez pas pourquoi les gens compliquent les choses:

objs.sort(function(a, b){
  return a.last_nom > b.last_nom;
});

Pour les moteurs plus stricts:

objs.sort(function(a, b){
  return a.last_nom == b.last_nom ? 0 : +(a.last_nom > b.last_nom) || -1;
});

Échangez l'opérateur pour le trier par ordre alphabétique inverse.

p3lim
la source
17
En fait, cela n'est pas correct car la fonction utilisée dans sort doit renvoyer -1, 0 ou 1, mais la fonction ci-dessus renvoie un booléen. Le tri fonctionne bien en chrome mais échoue dans PhantomJS par exemple. Voir code.google.com/p/phantomjs/issues/detail?id=1090
schup
Certains moteurs expliquent la stupidité, cette façon était en quelque sorte d'exploiter cela. J'ai mis à jour ma réponse avec une version appropriée.
p3lim
19
Je suggère de modifier pour retirer la première version. Son plus succinct semble donc plus attrayant, mais cela ne fonctionne pas, du moins pas de manière fiable. Si quelqu'un l'essaie dans un navigateur et que cela fonctionne, il se peut qu'il ne réalise même pas qu'il a un problème (surtout s'il n'a pas lu les commentaires). La deuxième version fonctionne correctement, il n'y a donc vraiment pas besoin de la première.
Kate
2
@Simon J'ai vraiment apprécié d'avoir d'abord la version "légèrement erronée", car la mise en œuvre plus stricte prend quelques secondes pour analyser et comprendre et serait beaucoup plus difficile sans elle.
Aaron Sherman
1
@ Lion789 il suffit de faire ceci:if(a.count == b.count) return a.name > b.name; else return a.count > b.count;
p3lim
62

Si vous avez des noms de famille en double, vous pouvez les trier par prénom -

obj.sort(function(a,b){
  if(a.last_nom< b.last_nom) return -1;
  if(a.last_nom >b.last_nom) return 1;
  if(a.first_nom< b.first_nom) return -1;
  if(a.first_nom >b.first_nom) return 1;
  return 0;
});
kennebec
la source
@BadFeelingAboutThis qu'est-ce que signifie retourner -1 ou 1? Je comprends que -1 signifie littéralement que A est inférieur à B uniquement par la syntaxe, mais pourquoi utiliser un 1 ou -1? Je vois que tout le monde utilise ces chiffres comme valeurs de retour, mais pourquoi? Merci.
Chris22
1
@ Chris22 un nombre négatif renvoyé signifie qu'il bdevrait venir après adans le tableau. Si un nombre positif est retourné, cela signifie adevrait venir après b. Si 0est retourné, cela signifie qu'ils sont considérés comme égaux. Vous pouvez toujours lire la documentation: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
BadFeelingAboutThis
@BadFeelingAboutThis merci, pour l'explication et le lien. Croyez-le ou non, j'ai googlé divers extraits de code en utilisant le 1, 0, -1avant de poser cette question ici. Je ne trouvais tout simplement pas les informations dont j'avais besoin.
Chris22
48

Solution simple et rapide à ce problème en utilisant l'héritage de prototype:

Array.prototype.sortBy = function(p) {
  return this.slice(0).sort(function(a,b) {
    return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
  });
}

Exemple / utilisation

objs = [{age:44,name:'vinay'},{age:24,name:'deepak'},{age:74,name:'suresh'}];

objs.sortBy('age');
// Returns
// [{"age":24,"name":"deepak"},{"age":44,"name":"vinay"},{"age":74,"name":"suresh"}]

objs.sortBy('name');
// Returns
// [{"age":24,"name":"deepak"},{"age":74,"name":"suresh"},{"age":44,"name":"vinay"}]

Mise à jour: ne modifie plus la baie d'origine.

Vinay Aggarwal
la source
5
Il ne renvoie pas simplement un autre tableau. mais trie en fait l'original!.
Vinay Aggarwal
Si vous voulez vous assurer que vous utilisez un tri naturel avec des nombres (c'est-à-dire 0,1,2,10,11 etc ...), utilisez parseInt avec l'ensemble Radix. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… so: return (parseInt (a [p], 10)> parseInt (b [p], 10))? 1: (parseInt (a [p], 10) <parseInt (b [p], 10))? -dix;
Paul
@codehuntr Merci de l'avoir corrigé. mais je suppose qu'au lieu de faire une fonction de tri pour effectuer cette sensibilisation, il est préférable de créer une fonction distincte pour fixer les types de données. Parce que la fonction de tri ne peut pas dire quelle propriété contiendra quel type de données. :)
Vinay Aggarwal
Très agréable. Inversez les flèches pour obtenir un effet asc / desc.
Abdul Sadik Yalcin
4
jamais, jamais proposer une solution qui modifie le prototype d'un objet principal
pbanka
39

À partir de 2018, il existe une solution beaucoup plus courte et élégante. Utilisez simplement. Array.prototype.sort () .

Exemple:

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

// sort by value
items.sort(function (a, b) {
  return a.value - b.value;
});
0leg
la source
7
Dans la question, des chaînes ont été utilisées à des fins de comparaison plutôt que des nombres. Votre réponse fonctionne très bien pour le tri par nombre, mais n'est pas si bonne pour les comparaisons par chaîne.
smcstewart
L' a.value - b.valueoutil utilisé pour comparer les attributs de l'objet (les nombres dans ce cas) peut être adopté pour les différentes durées des données. Par exemple, l'expression régulière peut être utilisée pour comparer chaque paire de chaînes voisines .
0leg
1
@ 0leg J'aimerais voir ici un exemple d'utilisation de l'expression régulière pour comparer des chaînes de cette manière.
Bob Stein
Cette implémentation est assez bonne si vous devez la trier par ID. Oui, vous avez suggéré d'utiliser regex pour comparer la chaîne voisine, ce qui rend la solution plus compliquée alors que le but de cette version simplifiée sera le cas contraire si regex est utilisé avec la solution donnée. La simplicité est la meilleure.
surendrapanday
33

Ancienne réponse incorrecte:

arr.sort((a, b) => a.name > b.name)

MISE À JOUR

Du commentaire de Beauchamp:

arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))

Format plus lisible:

arr.sort((a, b) => {
  if (a.name < b.name) return -1
  return a.name > b.name ? 1 : 0
})

Sans ternaires imbriqués:

arr.sort((a, b) => a.name < b.name ? - 1 : Number(a.name > b.name))

Explication: Number()lancera truevers 1et falsevers 0.

Damjan Pavlica
la source
Cela fonctionne, mais le résultat est instable pour une raison quelconque
TitanFighter
@ AO17 non. Vous ne pouvez pas soustraire des chaînes.
Patrick Roberts
15
Cela devrait le faire:arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))
Jean-François Beauchamp
3
@ Jean-FrançoisBeauchamp, votre solution fonctionne parfaitement bien et bien mieux.
Ahsan
le 3e avec Number est simple et sympa!
Kojo
29

Au lieu d'utiliser une fonction de comparaison personnalisée, vous pouvez également créer un type d'objet avec une toString()méthode personnalisée (qui est invoquée par la fonction de comparaison par défaut):

function Person(firstName, lastName) {
    this.firtName = firstName;
    this.lastName = lastName;
}

Person.prototype.toString = function() {
    return this.lastName + ', ' + this.firstName;
}

var persons = [ new Person('Lazslo', 'Jamf'), ...]
persons.sort();
Christoph
la source
29

Lodash.js (surensemble de Underscore.js )

Il est bon de ne pas ajouter de cadre pour chaque élément logique simple, mais s'appuyer sur des cadres d'utilitaires bien testés peut accélérer le développement et réduire la quantité de bogues.

Lodash produit du code très propre et favorise une programmation plus fonctionnelle style de . En un coup d'œil, il devient clair quelle est l'intention du code.

Le problème de l'OP peut simplement être résolu comme suit:

const sortedObjs = _.sortBy(objs, 'last_nom');

Plus d'informations? Par exemple, nous avons l'objet imbriqué suivant:

const users = [
  { 'user': {'name':'fred', 'age': 48}},
  { 'user': {'name':'barney', 'age': 36 }},
  { 'user': {'name':'wilma'}},
  { 'user': {'name':'betty', 'age': 32}}
];

Nous pouvons maintenant utiliser le raccourci _.propertyuser.age pour spécifier le chemin d'accès à la propriété qui doit correspondre. Nous allons trier les objets utilisateur par la propriété d'âge imbriquée. Oui, il permet la correspondance des propriétés imbriquées!

const sortedObjs = _.sortBy(users, ['user.age']);

Vous voulez qu'il soit inversé? Aucun problème. Utilisez _.reverse .

const sortedObjs = _.reverse(_.sortBy(users, ['user.age']));

Vous voulez combiner les deux à l'aide d'une chaîne ?

const { chain } = require('lodash');
const sortedObjs = chain(users).sortBy('user.age').reverse().value();

Ou quand préférez-vous couler sur la chaîne

const { flow, reverse, sortBy } = require('lodash/fp');
const sortedObjs = flow([sortBy('user.age'), reverse])(users); 
Nico Van Belle
la source
28

Vous pouvez utiliser

Moyen le plus simple : Lodash

( https://lodash.com/docs/4.17.10#orderBy )

Cette méthode est similaire à _.sortBy sauf qu'elle permet de spécifier les ordres de tri des itérés par lesquels trier. Si les commandes ne sont pas spécifiées, toutes les valeurs sont triées par ordre croissant. Sinon, spécifiez un ordre de "desc" pour décroissant ou "asc" pour ordre de tri croissant des valeurs correspondantes.

Arguments

collection (Array | Object): collection sur laquelle itérer. [iteratees = [_. identity]] (Array [] | Function [] | Object [] | string []): Les itérés à trier. [commandes] (chaîne []): les ordres de tri des itérés.

Retour

(Array): renvoie le nouveau tableau trié.


var _ = require('lodash');
var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

_.orderBy(homes, ['city', 'state', 'zip'], ['asc', 'desc', 'asc']);
Harvey
la source
23

Il y a beaucoup de bonnes réponses ici, mais je voudrais souligner qu'elles peuvent être étendues très simplement pour obtenir un tri beaucoup plus complexe. La seule chose que vous devez faire est d'utiliser l'opérateur OR pour chaîner des fonctions de comparaison comme ceci:

objs.sort((a,b)=> fn1(a,b) || fn2(a,b) || fn3(a,b) )

fn1,fn2 ... sont les fonctions de tri qui de retour [-1,0,1]. Il en résulte «tri par fn1», «tri par fn2» qui est à peu près égal à ORDER BY en SQL.

Cette solution est basée sur le comportement de l' ||opérateur qui évalue la première expression évaluée qui peut être convertie en true .

La forme la plus simple n'a qu'une seule fonction en ligne comme celle-ci:

// ORDER BY last_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) )

Ayant deux étapes avec last_nom, l' first_nomordre de tri ressemblerait à ceci:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) || 
                  a.first_nom.localeCompare(b.first_nom)  )

Une fonction de comparaison générique pourrait ressembler à ceci:

// ORDER BY <n>
let cmp = (a,b,n)=>a[n].localeCompare(b[n])

Cette fonction pourrait être étendue pour prendre en charge les champs numériques, la sensibilité à la casse, les types de données arbitraires, etc.

Vous pouvez les utiliser en les enchaînant par priorité de tri:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )
// ORDER_BY last_nom, first_nom DESC
objs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
// ORDER_BY last_nom DESC, first_nom DESC
objs.sort((a,b)=> -cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )

Le point ici est que le JavaScript pur avec une approche fonctionnelle peut vous mener loin sans bibliothèques externes ou code complexe. Il est également très efficace, car aucune analyse de chaîne ne doit être effectuée

Tero Tolonen
la source
23

Exemple d'utilisation:

objs.sort(sortBy('last_nom'));

Scénario:

/**
 * @description
 * Returns a function which will sort an
 * array of objects by the given key.
 *
 * @param  {String}  key
 * @param  {Boolean} reverse
 * @return {Function}
 */
const sortBy = (key, reverse) => {

  // Move smaller items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveSmaller = reverse ? 1 : -1;

  // Move larger items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveLarger = reverse ? -1 : 1;

  /**
   * @param  {*} a
   * @param  {*} b
   * @return {Number}
   */
  return (a, b) => {
    if (a[key] < b[key]) {
      return moveSmaller;
    }
    if (a[key] > b[key]) {
      return moveLarger;
    }
    return 0;
  };
};
Jamie Mason
la source
merci d'avoir décomposé cela, j'essaie de comprendre pourquoi les chiffres 1, 0, -1sont utilisés pour le tri. Même avec votre explication ci-dessus, qui semble très bonne - je ne comprends toujours pas très bien. Je pense toujours -1à l'utilisation de la propriété de longueur de tableau, c'est-à-dire: arr.length = -1signifie que l'élément n'est pas trouvé. Je mélange probablement les choses ici, mais pourriez-vous m'aider à comprendre pourquoi les chiffres 1, 0, -1sont utilisés pour déterminer l'ordre? Merci.
Chris22
1
Ce n'est pas tout à fait exact, mais cela pourrait aider à penser comme ceci: la fonction passée à array.sort est appelée une fois pour chaque élément du tableau, comme l'argument nommé "a". La valeur de retour de chaque appel de fonction est la façon dont l'index (numéro de position actuel) de l'élément "a" doit être modifié par rapport à l'élément suivant "b". L'index dicte l'ordre du tableau (0, 1, 2 etc) Donc si "a" est à l'index 5 et que vous retournez -1 puis 5 + -1 == 4 (déplacez-le plus près vers l'avant) 5 + 0 == 5 (conservez-le où il est) etc. Il parcourt le tableau en comparant 2 voisins à chaque fois jusqu'à ce qu'il atteigne la fin, laissant un tableau trié.
Jamie Mason
merci d'avoir pris le temps de l'expliquer davantage. Donc, en utilisant votre explication et le MDN Array.prototype.sort , je vais vous dire ce que je pense de ceci: par rapport à aet b, sia est supérieur à bajouter 1 à l'index de aet le placer derrière b, si aest inférieur à b, soustrayez 1 de aet placez-le devant b. Si aet bsont identiques, ajoutez 0 à aet laissez-le où il est.
Chris22
22

Je n'ai pas vu cette approche particulière suggérée, alors voici une méthode de comparaison laconique que j'aime utiliser qui fonctionne pour les deux stringet number:

const objs = [ 
  { first_nom: 'Lazslo', last_nom: 'Jamf'     },
  { first_nom: 'Pig',    last_nom: 'Bodine'   },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

const sortBy = fn => (a, b) => {
  const fa = fn(a);
  const fb = fn(b);
  return -(fa < fb) || +(fa > fb);
};

const getLastName = o => o.last_nom;
const sortByLastName = sortBy(getLastName);

objs.sort(sortByLastName);
console.log(objs.map(getLastName));

Voici une explication de sortBy():

sortBy()accepte un fnqui sélectionne la valeur d'un objet à utiliser comme comparaison et renvoie une fonction qui peut être transmise directement à Array.prototype.sort(). Dans cet exemple, nous utilisons o.last_nomcomme valeur de comparaison, donc à chaque fois que nous recevons deux objets, Array.prototype.sort()tels que

{ first_nom: 'Lazslo', last_nom: 'Jamf' }

et

{ first_nom: 'Pig', last_nom: 'Bodine' }

nous utilisons

(a, b) => {
  const fa = fn(a);
  const fb = fn(b);
  return -(fa < fb) || +(fa > fb);
};

pour les comparer.

En se souvenant de cela fn = o => o.last_nom, nous pouvons étendre la fonction de comparaison à l'équivalent

(a, b) => {
  const fa = a.last_nom;
  const fb = b.last_nom;
  return -(fa < fb) || +(fa > fb);
};

L' ||opérateur logique OU possède une fonctionnalité de court-circuit qui est très utile ici. En raison de son fonctionnement, le corps de la fonction ci-dessus signifie

if (fa < fb) return -1;
if (fa > fb) return 1;
return 0;

En prime, voici l'équivalent dans ECMAScript 5 sans fonctions fléchées, qui est malheureusement plus détaillé:

var objs = [ 
  { first_nom: 'Lazslo', last_nom: 'Jamf'     },
  { first_nom: 'Pig',    last_nom: 'Bodine'   },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

var sortBy = function (fn) {
  return function (a, b) {
    var fa = fn(a);
    var fb = fn(b);
    return -(fa < fb) || +(fa > fb);
  };
};

var getLastName = function (o) { return o.last_nom };
var sortByLastName = sortBy(getLastName);

objs.sort(sortByLastName);
console.log(objs.map(getLastName));

Patrick Roberts
la source
17

Je sais que cette question est trop ancienne, mais je n'ai vu aucune implémentation similaire à la mienne.
Cette version est basée sur l' idiome de transformation schwartzienne .

function sortByAttribute(array, ...attrs) {
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => {
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return {
      getter: o => o[pred],
      descend: descending
    };
  });
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => {
    return {
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    };
  })
  .sort((o1, o2) => {
    let i = -1, result = 0;
    while (++i < predicates.length) {
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    }
    return result;
  })
  .map(item => item.src);
}

Voici un exemple d'utilisation:

let games = [
  { name: 'Mashraki',          rating: 4.21 },
  { name: 'Hill Climb Racing', rating: 3.88 },
  { name: 'Angry Birds Space', rating: 3.88 },
  { name: 'Badland',           rating: 4.33 }
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));
a8m
la source
14

Tri (plus) Tableaux complexes d'objets

Comme vous rencontrez probablement des structures de données plus complexes comme ce tableau, j'élargirais la solution.

TL; DR

Sont une version plus enfichable basée sur la très belle réponse de @ ege-Özcan .

Problème

J'ai rencontré ce qui suit et je n'ai pas pu le changer. Je ne voulais pas non plus aplatir temporairement l'objet. Je ne voulais pas non plus utiliser le soulignement / lodash, principalement pour des raisons de performances et pour le plaisir de l'implémenter moi-même.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

Objectif

Le but est de le trier principalement par People.Name.nameet secondairement parPeople.Name.surname

Obstacles

Maintenant, dans la solution de base, utilise la notation entre crochets pour calculer les propriétés à trier dynamiquement. Ici, cependant, nous devons également construire dynamiquement la notation des crochets, car vous vous attendez à ce que certainsPeople['Name.name'] fonctionnent - ce qui ne fonctionne pas.

Le simple fait People['Name']['name'], en revanche, est statique et vous permet uniquement de descendre le n niveau -ème.

Solution

Le principal ajout ici sera de parcourir l'arborescence des objets et de déterminer la valeur de la dernière feuille, vous devez spécifier, ainsi que toute feuille intermédiaire.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

People.sort(dynamicMultiSort(['Name','name'], ['Name', '-surname']));
// Results in...
// [ { Name: { name: 'AAA', surname: 'ZZZ' }, Middlename: 'Abrams' },
//   { Name: { name: 'Name', surname: 'Surname' }, Middlename: 'JJ' },
//   { Name: { name: 'Name', surname: 'AAA' }, Middlename: 'Wars' } ]

// same logic as above, but strong deviation for dynamic properties 
function dynamicSort(properties) {
  var sortOrder = 1;
  // determine sort order by checking sign of last element of array
  if(properties[properties.length - 1][0] === "-") {
    sortOrder = -1;
    // Chop off sign
    properties[properties.length - 1] = properties[properties.length - 1].substr(1);
  }
  return function (a,b) {
    propertyOfA = recurseObjProp(a, properties)
    propertyOfB = recurseObjProp(b, properties)
    var result = (propertyOfA < propertyOfB) ? -1 : (propertyOfA > propertyOfB) ? 1 : 0;
    return result * sortOrder;
  };
}

/**
 * Takes an object and recurses down the tree to a target leaf and returns it value
 * @param  {Object} root - Object to be traversed.
 * @param  {Array} leafs - Array of downwards traversal. To access the value: {parent:{ child: 'value'}} -> ['parent','child']
 * @param  {Number} index - Must not be set, since it is implicit.
 * @return {String|Number}       The property, which is to be compared by sort.
 */
function recurseObjProp(root, leafs, index) {
  index ? index : index = 0
  var upper = root
  // walk down one level
  lower = upper[leafs[index]]
  // Check if last leaf has been hit by having gone one step too far.
  // If so, return result from last step.
  if (!lower) {
    return upper
  }
  // Else: recurse!
  index++
  // HINT: Bug was here, for not explicitly returning function
  // https://stackoverflow.com/a/17528613/3580261
  return recurseObjProp(lower, leafs, index)
}

/**
 * Multi-sort your array by a set of properties
 * @param {...Array} Arrays to access values in the form of: {parent:{ child: 'value'}} -> ['parent','child']
 * @return {Number} Number - number for sort algorithm
 */
function dynamicMultiSort() {
  var args = Array.prototype.slice.call(arguments); // slight deviation to base

  return function (a, b) {
    var i = 0, result = 0, numberOfProperties = args.length;
    // REVIEW: slightly verbose; maybe no way around because of `.sort`-'s nature
    // Consider: `.forEach()`
    while(result === 0 && i < numberOfProperties) {
      result = dynamicSort(args[i])(a, b);
      i++;
    }
    return result;
  }
}

Exemple

Exemple de travail sur JSBin

eljefedelrodeodeljefe
la source
2
Pourquoi? Ce n'est pas la réponse à la question d'origine et "l'objectif" pourrait être résolu simplement avec People.sort ((a, b) => {return a.Name.name.localeCompare (b.Name.name) || a.Name .surname.localeCompare (b.Name.surname)})
Tero Tolonen
12

Encore une option:

var someArray = [...];

function generateSortFn(prop, reverse) {
    return function (a, b) {
        if (a[prop] < b[prop]) return reverse ? 1 : -1;
        if (a[prop] > b[prop]) return reverse ? -1 : 1;
        return 0;
    };
}

someArray.sort(generateSortFn('name', true));

tri croissant par défaut.

Ravshan Samandarov
la source
1
La version légèrement modifiée pour le tri par plusieurs champs est ici si nécessaire: stackoverflow.com/questions/6913512/…
Ravshan Samandarov
il semble que ce pourrait être le suivant: fonction d'exportation generateSortFn (prop: string, reverse: boolean = false): (... args: any) => number {return (a, b) => {return a [prop ] <b [prop]? inverser ? 1: -1: a [prop]> b [prop]? inverser ? -1: 1: 0; }; }
Den Kerny
Je préférerais un code plus lisible que le plus court.
Ravshan Samandarov
d'accord, mais dans certains cas, je n'ai pas besoin de regarder les fonctions utilitaires.
Den Kerny,
11

Une fonction simple qui trie un tableau d'objets par une propriété

function sortArray(array, property, direction) {
    direction = direction || 1;
    array.sort(function compare(a, b) {
        let comparison = 0;
        if (a[property] > b[property]) {
            comparison = 1 * direction;
        } else if (a[property] < b[property]) {
            comparison = -1 * direction;
        }
        return comparison;
    });
    return array; // Chainable
}

Usage:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

sortArray(objs, "last_nom"); // Asc
sortArray(objs, "last_nom", -1); // Desc
François Girard
la source
Cette solution a parfaitement fonctionné pour moi pour trier dans les deux sens. Merci u
manjuvreddy
10

Un moyen simple:

objs.sort(function(a,b) {
  return b.last_nom.toLowerCase() < a.last_nom.toLowerCase();
});

Voir cela '.toLowerCase()'est nécessaire pour éviter les erreurs dans la comparaison des chaînes.

Caio Ladislau
la source
4
Vous pouvez utiliser les fonctions fléchées pour rendre le code un peu plus élégant:objs.sort( (a,b) => b.last_nom.toLowerCase() < a.last_nom.toLowerCase() );
Sertage
C'est faux pour la même raison que celle expliquée ici .
Patrick Roberts
2
Les fonctions fléchées ne sont pas valables pour ES5. Des tonnes de moteurs sont encore limitées à ES5. Dans mon cas, je trouve la réponse ci-dessus nettement meilleure puisque je suis sur un moteur ES5 (forcé par mon entreprise)
dylanh724
9

paramètres de desc supplémentaires pour le code Ege Özcan

function dynamicSort(property, desc) {
    if (desc) {
        return function (a, b) {
            return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
        }   
    }
    return function (a, b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}
Behnam Shomali
la source
9

En combinant la solution dynamique d'Ege avec l'idée de Vinay, vous obtenez une belle solution robuste:

Array.prototype.sortBy = function() {
    function _sortByAttr(attr) {
        var sortOrder = 1;
        if (attr[0] == "-") {
            sortOrder = -1;
            attr = attr.substr(1);
        }
        return function(a, b) {
            var result = (a[attr] < b[attr]) ? -1 : (a[attr] > b[attr]) ? 1 : 0;
            return result * sortOrder;
        }
    }
    function _getSortFunc() {
        if (arguments.length == 0) {
            throw "Zero length arguments not allowed for Array.sortBy()";
        }
        var args = arguments;
        return function(a, b) {
            for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
                result = _sortByAttr(args[i])(a, b);
            }
            return result;
        }
    }
    return this.sort(_getSortFunc.apply(null, arguments));
}

Usage:

// Utility for printing objects
Array.prototype.print = function(title) {
    console.log("************************************************************************");
    console.log("**** "+title);
    console.log("************************************************************************");
    for (var i = 0; i < this.length; i++) {
        console.log("Name: "+this[i].FirstName, this[i].LastName, "Age: "+this[i].Age);
    }
}

// Setup sample data
var arrObj = [
    {FirstName: "Zach", LastName: "Emergency", Age: 35},
    {FirstName: "Nancy", LastName: "Nurse", Age: 27},
    {FirstName: "Ethel", LastName: "Emergency", Age: 42},
    {FirstName: "Nina", LastName: "Nurse", Age: 48},
    {FirstName: "Anthony", LastName: "Emergency", Age: 44},
    {FirstName: "Nina", LastName: "Nurse", Age: 32},
    {FirstName: "Ed", LastName: "Emergency", Age: 28},
    {FirstName: "Peter", LastName: "Physician", Age: 58},
    {FirstName: "Al", LastName: "Emergency", Age: 51},
    {FirstName: "Ruth", LastName: "Registration", Age: 62},
    {FirstName: "Ed", LastName: "Emergency", Age: 38},
    {FirstName: "Tammy", LastName: "Triage", Age: 29},
    {FirstName: "Alan", LastName: "Emergency", Age: 60},
    {FirstName: "Nina", LastName: "Nurse", Age: 54}
];

//Unit Tests
arrObj.sortBy("LastName").print("LastName Ascending");
arrObj.sortBy("-LastName").print("LastName Descending");
arrObj.sortBy("LastName", "FirstName", "-Age").print("LastName Ascending, FirstName Ascending, Age Descending");
arrObj.sortBy("-FirstName", "Age").print("FirstName Descending, Age Ascending");
arrObj.sortBy("-Age").print("Age Descending");
Mike R
la source
1
Merci pour l'idée! Soit dit en passant, n'encouragez pas les gens à changer le prototype de tableau (voir l'avertissement à la fin de mon exemple).
Ege Özcan
8

Selon votre exemple, vous devez trier par deux champs (nom, prénom), plutôt qu'un. Vous pouvez utiliser la bibliothèque Alasql pour effectuer ce tri sur une seule ligne:

var res = alasql('SELECT * FROM ? ORDER BY last_nom, first_nom',[objs]);

Essayez cet exemple sur jsFiddle .

agershun
la source
8
objs.sort(function(a,b){return b.last_nom>a.last_nom})
Roshni Bokade
la source
1
En fait, cela ne semblait pas fonctionner, il fallait utiliser la réponse acceptée. Il ne triait pas correctement.
madprops
8

Étant donné l'exemple d'origine:

var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];

Trier par plusieurs champs:

objs.sort(function(left, right) {
    var last_nom_order = left.last_nom.localeCompare(right.last_nom);
    var first_nom_order = left.first_nom.localeCompare(right.first_nom);
    return last_nom_order || first_nom_order;
});

Remarques

  • a.localeCompare(b)est universellement supporté et retourne -1,0,1 si a<b, a==b, a>brespectivement.
  • ||dans la dernière ligne donne la last_nompriorité surfirst_nom .
  • La soustraction fonctionne sur les champs numériques: var age_order = left.age - right.age;
  • Annuler pour inverser l'ordre, return -last_nom_order || -first_nom_order || -age_order;
Bob Stein
la source
8

Essaye ça,

UPTO ES5

//Ascending Sort
items.sort(function (a, b) {
   return a.value - b.value;
});


//Descending Sort
items.sort(function (a, b) {
   return b.value - a.value;
});


IN ES6 & above:

// Ascending sort
items.sort((a, b) => a.value - b.value);

// Descending Sort
 items.sort((a, b) => b.value - a.value);
Abhishek
la source
meilleure solution et facile
Omar Hasan
7

Vous devrez peut-être les convertir en minuscules afin d'éviter toute confusion.

objs.sort(function (a,b) {

var nameA=a.last_nom.toLowerCase(), nameB=b.last_nom.toLowerCase()

if (nameA < nameB)
  return -1;
if (nameA > nameB)
  return 1;
return 0;  //no sorting

})
Burak Keceli
la source
7
function compare(propName) {
    return function(a,b) {
        if (a[propName] < b[propName])
            return -1;
        if (a[propName] > b[propName])
            return 1;
        return 0;
    };
}

objs.sort(compare("last_nom"));
Evgenii
la source
1
Veuillez envisager de modifier votre message pour ajouter plus d'explications sur ce que fait votre code et pourquoi il résoudra le problème. Une réponse qui contient principalement du code (même si cela fonctionne) n'aidera généralement pas l'OP à comprendre leur problème.
Drenmi
7

Avec Ramda,

npm install ramda

import R from 'ramda'
var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];
var ascendingSortedObjs = R.sortBy(R.prop('last_nom'), objs)
var descendingSortedObjs = R.reverse(ascendingSortedObjs)
Sridhar Sg
la source
6

C'est un problème simple, je ne sais pas pourquoi les gens ont une solution aussi complexe.
Une fonction de tri simple (basée sur l' algorithme de tri rapide):

function sortObjectsArray(objectsArray, sortKey)
        {
            // Quick Sort:
            var retVal;

            if (1 < objectsArray.length)
            {
                var pivotIndex = Math.floor((objectsArray.length - 1) / 2);  // middle index
                var pivotItem = objectsArray[pivotIndex];                    // value in the middle index
                var less = [], more = [];

                objectsArray.splice(pivotIndex, 1);                          // remove the item in the pivot position
                objectsArray.forEach(function(value, index, array)
                {
                    value[sortKey] <= pivotItem[sortKey] ?                   // compare the 'sortKey' proiperty
                        less.push(value) :
                        more.push(value) ;
                });

                retVal = sortObjectsArray(less, sortKey).concat([pivotItem], sortObjectsArray(more, sortKey));
            }
            else
            {
                retVal = objectsArray;
            }

            return retVal;
        }

Exemple d'utilisation:

var myArr = 
        [
            { val: 'x', idx: 3 },
            { val: 'y', idx: 2 },
            { val: 'z', idx: 5 },
        ];
myArr = sortObjectsArray(myArr, 'idx');
Gil Epshtain
la source
5
En quoi l'implémentation du tri rapide dans js est-elle une solution simple? Algorithme simple mais pas une solution simple.
Andrew
C'est simple car il n'utilise aucune bibliothèque externe et ne modifie pas le prototype de l'objet. À mon avis, la longueur du code n'a pas d'impact direct sur la complexité du code
Gil Epshtain
2
Eh bien, laissez-moi essayer avec des mots différents: comment réinventer la roue est une solution simple?
Roberto14