Le tri est une méthode JavaScript super pratique qui peut afficher les valeurs d’un tableau dans un certain ordre. Qu’il s’agisse d’annonces immobilières par prix, de restaurants à hamburgers par distance ou de meilleurs moments de bonheur à proximité par classement, le tri des tableaux d’information est un besoin commun.

Si vous le faites déjà avec JavaScript sur certains projet, vous aimerez probablement utiliser la méthode intégrée array.sort, qui fait partie de la même famille des méthodes de tableau qui inclut .filter, .map et .reduce.

Jetons un coup d’oeil à la façon de le faire !

Un petit mot sur les effets secondaires

Avant d’entrer dans les détails sur la façon d’utiliser .sort, il y a un détail très important qui doit être abordé. Alors que la plupart des méthodes de tableau ES5 telles que .filter, .map et .reduce retournent un nouveau tableau et laissent l’original intact, .sort triera le tableau en place. Si cela n’est pas désiré, une technique ES6 pour l’éviter consiste à utiliser l’opérateur ... pour créer un nouveau tableau manuellement.

const foo = ['c','b','a'];
const bar = ['x','z','y'];
const fooSorted = foo.sort();
const barSorted = [...bar].sort();

console.log({foo, fooSorted, bar, barSorted});

/*
{
  "foo":       [ "a", "b", "c" ],
  "fooSorted": [ "a", "b", "c" ],
  "bar":       [ "x", "z", "y" ],
  "barSorted": [ "x", "y", "z" ]
}
*/

foo et fooSorted font tous deux référence au même tableau, mais bar et barSorted sont maintenant des tableaux individuels.

Aperçu général de la methode .sort

Le seul paramètre de la méthode .sort est une fonction. La spécification s’y réfère comme compareFn – j’y ferai référence comme la “fonction de comparaison” pour le reste de l’article. Cette fonction de comparaison accepte deux paramètres, que j’appellerai a et b. a et b sont les deux éléments que nous allons comparer. Si vous ne fournissez pas de fonction de comparaison, le tableau va contraindre chaque élément dans une chaîne et le trier selon les points Unicode.

Si vous voulez que le a soit ordonné en premier dans le tableau, la fonction de comparaison doit retourner un entier négatif ; pour b, un entier positif. Si vous voulez que les deux maintiennent leur ordre actuel, retournez un 0.

Si vous ne comprenez pas, ne vous inquiétez pas ! Espérons que cela deviendra beaucoup plus clair avec quelques exemples.

Comparaison numérique avec .sort

L’une des fonctions de rappel les plus simples à écrire est une comparaison numérique.

const numbers = [13,8,2,21,5,1,3,1];
const byValue = (a,b) => a - b;
const sorted = [...numbers].sort(byValue);
console.log(sorted); // [1,1,2,3,5,8,13,21]

Si a est supérieur à b, a - b retournera un nombre positif, donc b sera trié en premier.

Comparaison de chaîne de caractère avec .sort

Lors de la comparaison de chaînes, les opérateurs > et < compareront les valeurs basées sur la valeur Unicode de chaque chaîne. Cela signifie que toutes les lettres majuscules seront “inférieures” à toutes les lettres minuscules, ce qui peut conduire à un comportement inattendu.

JavaScript a une méthode pour aider à comparer les chaînes : la méthode String.prototype.localeCompare. Cette méthode accepte une chaîne de comparaison, une locale et un objet options. L’objet options accepte quelques propriétés (que vous pouvez voir ici), mais je trouve que la plus utile est “sensitivity”. Cela affectera la façon dont les comparaisons entre les variations de lettres telles que la casse et l’accent fonctionneront.

const strings = ['Über', 'alpha', 'Zeal', 'über', 'uber', 'Uber', 'Alpha', 'zeal'];

const sortBySensitivity = sensitivity => (a, b) => a.localeCompare(
  b,
  undefined, // locale string -- undefined means to use browser default
  { sensitivity }
);

const byAccent  = sortBySensitivity('accent');
const byBase    = sortBySensitivity('base');
const byCase    = sortBySensitivity('case');
const byVariant = sortBySensitivity('variant'); // default

const accentSorted  = [...strings].sort(byAccent);
const baseSorted    = [...strings].sort(byBase);
const caseSorted    = [...strings].sort(byCase);
const variantSorted = [...strings].sort(byVariant);

console.log({accentSorted, baseSorted, caseSorted, variantSorted});

/*
{
  "accentSorted":  [ "alpha", "Alpha", "uber", "Uber", "Über", "über", "Zeal", "zeal" ],
  "baseSorted":    [ "alpha", "Alpha", "Über", "über", "uber", "Uber", "Zeal", "zeal" ],
  "caseSorted":    [ "alpha", "Alpha", "über", "uber", "Über", "Uber", "zeal", "Zeal" ],
  "variantSorted": [ "alpha", "Alpha", "uber", "Uber", "über", "Über", "zeal", "Zeal" ]
}
*/

Pour moi, baseSorted semble être le plus logique pour la plupart des tris alphabétiques -‘ü’, u’,’u’,’Ü’, et’U’ sont équivalents, donc ils restent dans l’ordre du tableau original.

Fonctions d’exécution avant la comparaison des valeurs

Vous pouvez exécuter une fonction de comparaison sur une valeur dérivée de l’élément de chaque tableau. Tout d’abord, écrivons une usine de fonction de comparaison qui va “mapper” l’élément avant d’appeler la fonction de comparaison.

const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b));

Un cas d’utilisation pour cela est le tri basé sur l’attribut d’un objet.

const purchases = [
  { name: 'Popcorn', price: 5.75 }, 
  { name: 'Movie Ticket', price: 12 },
  { name: 'Soda', price: 3.75 },
  { name: 'Candy', price: 5 },
];

const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b));
const byValue = (a,b) => a - b;
const toPrice = e => e.price;
const byPrice = sortByMapped(toPrice,byValue);

console.log([...purchases].sort(byPrice));

/*
[
  { name: "Soda", price: 3.75 },
  { name: "Candy", price: 5 },
  { name: "Popcorn", price: 5.75 },
  { name: "Movie Ticket", price: 12 }
]
*/

Un autre cas pourrait être de comparer une série de dates.

const dates  = ['2018-12-10', '1991-02-10', '2015-10-07', '1990-01-11'];
const sortByMapped = (map,compareFn) => (a,b) => compareFn(map(a),map(b));
const toDate = e => new Date(e).getTime();
const byValue = (a,b) => a - b;
const byDate = sortByMapped(toDate,byValue);

console.log([...dates].sort(byDate));
// ["1990-01-11", "1991-02-10", "2015-10-07", "2018-12-10"]

Inverser un tri avec .sort

Dans certains cas, vous pouvez vouloir annuler le résultat d’une fonction de comparaison. C’est subtilement différent que de faire un tri puis d’inverser le résultat à cause de la façon dont les liaisons sont traitées : si vous inversez le résultat, les liaisons seront également inversées.

Pour écrire une fonction d’ordre améliorée qui accepte une fonction de comparaison et en retourne une nouvelle, vous devez retourner le signe de la valeur de retour de la comparaison.

const flipComparison = fn => (a,b) => -fn(a,b);
const byAlpha = (a,b) => a.localeCompare(b, null, { sensitivity: 'base' });
const byReverseAlpha = flipComparison(byAlpha);

console.log(['A', 'B', 'C'].sort(byReverseAlpha)); // ['C','B','A']

Exécution d’un tri décisif (en cas d’égalité)

Il y a des moments où vous voudrez peut-être avoir un tri au “tie-breaker” – c’est-à-dire, une autre fonction de comparaison qui est utilisée dans le cas d’une égalité.

En utilisant [].reduce, vous pouvez aplatir un tableau de fonctions de comparaison en une seule.

const sortByMapped = map => compareFn => (a,b) => compareFn(map(a),map(b));
const flipComparison = fn => (a,b) => -fn(a,b);
const byValue = (a,b) => a - b;

const byPrice = sortByMapped(e => e.price)(byValue);
const byRating = sortByMapped(e => e.rating)(flipComparison(byValue));

const sortByFlattened = fns => (a,b) => 
  fns.reduce((acc, fn) => acc || fn(a,b), 0);

const byPriceRating = sortByFlattened([byPrice,byRating]);

const restaurants = [
  { name: "Foo's Burger Stand", price: 1, rating: 3 },
  { name: "The Tapas Bar", price: 3, rating: 4 },
  { name: "Baz Pizza", price: 3, rating: 2 },
  { name: "Amazing Deal", price: 1, rating: 5 },
  { name: "Overpriced", price: 5, rating: 1 }, 
];

console.log(restaurants.sort(byPriceRating));

/*
{name: "Amazing Deal", price: 1, rating: 5}
{name: "Foo's Burger Stand", price: 1, rating: 3}
{name: "The Tapas Bar", price: 3, rating: 4}
{name: "Baz Pizza", price: 3, rating: 2}
{name: "Overpriced", price: 5, rating: 1}
*/

Écriture d’un tri aléatoire avec .sort

Vous voudrez peut-être trier un tableau “au hasard”. Une technique que j’ai vue est d’utiliser la fonction suivante comme fonction de comparaison.

const byRandom = () => Math.random() - .5;

Puisque Math.random() renvoie un nombre “aléatoire” entre 0 et 1, la fonction byRandom devrait renvoyer un nombre positif la moitié du temps et un nombre négatif l’autre moitié. Cela semble être une bonne solution, mais malheureusement, comme la fonction de comparaison n’est pas “consistante” – ce qui signifie qu’elle peut ne pas retourner la même valeur lorsqu’elle est appelée plusieurs fois avec les mêmes valeurs – elle peut donner des résultats imprévus.

Par exemple, prenons un tableau de nombres entre 0 et 4. Si cette fonction byRandom était vraiment aléatoire, on pourrait s’attendre à ce que le nouvel index de chaque nombre soit réparti également sur un nombre suffisant d’itérations. La valeur 0 d’origine serait tout aussi susceptible d’être dans l’index 4 que l’index 0 dans le nouveau tableau. Cependant, dans la pratique, cette fonction va biaiser chaque numéro à sa position d’origine.

See the Pen Array.sort() tri aléatoire by Siddhy (@Siddhyn) on CodePen.

La “diagonale” en haut à gauche aura statistiquement la plus grande valeur. Dans un tri idéal et vraiment aléatoire, chaque cellule du tableau retournerait environs 20 %.

La solution consiste à trouver un moyen de s’assurer que la fonction de comparaison reste consistante. Une façon de le faire est de mapper la valeur aléatoire de chaque élément du tableau avant la comparaison, puis de la mapper après.

const sortByMapped = map => compareFn => (a,b) => compareFn(map(a),map(b));
const values = [0,1,2,3,4,5,6,7,8,9];
const withRandom = (e) => ({ random: Math.random(), original: e });
const toOriginal = ({original}) => original;
const toRandom = ({random}) => random;
const byValue = (a,b) => a - b;
const byRandom = sortByMapped(toRandom)(byValue);

const shuffleArray = array => array
  .map(withRandom)
  .sort(byRandom)
  .map(toOriginal);

Ainsi, chaque élément a une valeur aléatoire unique qui n’est calculée qu’une seule fois par élément plutôt qu’une seule fois par comparaison. Ceci élimine le biais de tri vers la position d’origine.

See the Pen Array.sort() Tri aléatoire avec methode consistante by Siddhy (@Siddhyn) on CodePen.

Conclusion

La methode .sort est vraiment puissante et sympa à utiliser. N’hésitez pas à vous en servir et dites moi ce que vous en pensez en commentaire ! Et si vous avez le temps, passez voir mon article sur comment utiliser les boucles en javascript 😉


siddhy

Développeur web full stack depuis une 15aine d'année dans une agence web du sud de la France et Geek depuis toujours, l'apprentissage et le partage font parti intégrante de ma philosophie au même titre que l'évolution personnelle et la sagesse bouddhiste.

0 commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *