Comment trier correctement les chaînes contenant des nombres intégrés

Utilisez le classement numérique pour trier les noms de fichiers, les numéros de version et autres chaînes contenant des nombres dans un ordre naturel

Introduction

Lorsque vous triez des chaînes contenant des nombres, vous vous attendez à ce que file1.txt, file2.txt et file10.txt apparaissent dans cet ordre. Cependant, la comparaison de chaînes par défaut de JavaScript produit file1.txt, file10.txt, file2.txt à la place. Cela se produit car les chaînes sont comparées caractère par caractère, et le caractère 1 dans 10 vient avant le caractère 2.

Ce problème apparaît chaque fois que vous triez des noms de fichiers, des numéros de version, des adresses postales, des codes produits ou toute autre chaîne contenant des nombres intégrés. L'ordre incorrect déroute les utilisateurs et rend les données difficiles à parcourir.

JavaScript fournit l'API Intl.Collator avec une option numérique qui résout ce problème. Cette leçon explique comment fonctionne le classement numérique, pourquoi la comparaison de chaînes par défaut échoue et comment trier les chaînes contenant des nombres intégrés dans un ordre numérique naturel.

Qu'est-ce que le classement numérique

Le classement numérique est une méthode de comparaison qui traite les séquences de chiffres comme des nombres plutôt que comme des caractères individuels. Lors de la comparaison de chaînes, le collateur identifie les séquences de chiffres et les compare selon leur valeur numérique.

Avec le classement numérique désactivé, la chaîne file10.txt vient avant file2.txt car la comparaison caractère par caractère trouve que 1 vient avant 2 à la première position différente. Le collateur ne considère jamais que 10 représente un nombre supérieur à 2.

Avec le classement numérique activé, le collateur reconnaît 10 et 2 comme des nombres complets et les compare numériquement. Puisque 10 est supérieur à 2, file2.txt vient correctement avant file10.txt.

Ce comportement produit ce que l'on appelle le tri naturel ou l'ordre naturel, où les chaînes contenant des nombres sont triées de la manière attendue par les humains plutôt que strictement alphabétiquement.

Pourquoi la comparaison de chaînes par défaut échoue pour les nombres

La comparaison de chaînes par défaut de JavaScript utilise l'ordre lexicographique, qui compare les chaînes caractère par caractère de gauche à droite en utilisant les valeurs de point de code Unicode. Cela fonctionne correctement pour le texte alphabétique mais produit des résultats inattendus pour les nombres.

Considérez comment la comparaison lexicographique gère ces chaînes :

const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
files.sort();

console.log(files);
// Output: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']

La comparaison examine chaque position de caractère indépendamment. À la première position différente après file, elle compare 1 avec 2. Puisque 1 a une valeur Unicode inférieure à 2, toute chaîne commençant par file1 vient avant toute chaîne commençant par file2, indépendamment de ce qui suit.

Cela produit la séquence file1.txt, file10.txt, file2.txt, file20.txt, ce qui viole les attentes humaines concernant l'ordre des nombres.

Utilisation d'Intl.Collator avec l'option numeric

Le constructeur Intl.Collator accepte un objet d'options avec une propriété numeric. Définir numeric: true active la collation numérique, ce qui amène le collateur à comparer les séquences de chiffres par leur valeur numérique.

const collator = new Intl.Collator('en-US', { numeric: true });
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];

files.sort(collator.compare);

console.log(files);
// Output: ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt']

La méthode compare du collateur renvoie un nombre négatif lorsque le premier argument doit venir avant le second, zéro lorsqu'ils sont égaux, et un nombre positif lorsque le premier doit venir après le second. Cela correspond à la signature attendue par la méthode Array.sort() de JavaScript.

Le résultat trié place les fichiers dans un ordre numérique naturel. Le collateur reconnaît que 1 < 2 < 10 < 20, produisant la séquence attendue par les humains.

Tri de chaînes alphanumériques mixtes

La collation numérique gère les chaînes où les nombres apparaissent à n'importe quelle position, pas seulement à la fin. Le collateur compare les portions alphabétiques normalement et les portions numériques numériquement.

const collator = new Intl.Collator('en-US', { numeric: true });
const addresses = ['123 Oak St', '45 Oak St', '1234 Oak St', '5 Oak St'];

addresses.sort(collator.compare);

console.log(addresses);
// Output: ['5 Oak St', '45 Oak St', '123 Oak St', '1234 Oak St']

Le collateur identifie les séquences de chiffres au début de chaque chaîne et les compare numériquement. Il reconnaît que 5 < 45 < 123 < 1234, même si la comparaison lexicographique produirait un ordre différent.

Tri des numéros de version

Les numéros de version constituent un cas d'usage courant pour la collation numérique. Les versions logicielles comme 1.2.10 devraient venir après 1.2.2, mais la comparaison lexicographique produit un ordre incorrect.

const collator = new Intl.Collator('en-US', { numeric: true });
const versions = ['1.2.10', '1.2.2', '1.10.5', '1.2.5'];

versions.sort(collator.compare);

console.log(versions);
// Output: ['1.2.2', '1.2.5', '1.2.10', '1.10.5']

Le collateur compare correctement chaque composant numérique. Dans la séquence 1.2.2, 1.2.5, 1.2.10, il reconnaît que le troisième composant augmente numériquement. Dans 1.10.5, il reconnaît que le deuxième composant est 10, ce qui est supérieur à 2.

Utilisation avec les codes produits et identifiants

Les codes produits, numéros de facture et autres identifiants mélangent souvent des lettres avec des nombres. La collation numérique garantit que ceux-ci sont triés dans un ordre logique.

const collator = new Intl.Collator('en-US', { numeric: true });
const products = ['PROD-1', 'PROD-10', 'PROD-2', 'PROD-100'];

products.sort(collator.compare);

console.log(products);
// Output: ['PROD-1', 'PROD-2', 'PROD-10', 'PROD-100']

Le préfixe alphabétique PROD- correspond dans toutes les chaînes, donc le collateur compare le suffixe numérique. Le résultat reflète un ordre numérique croissant plutôt qu'un ordre lexicographique.

Tri avec différentes locales

L'option numeric fonctionne avec n'importe quelle locale. Bien que différentes locales puissent avoir des règles de tri différentes pour les caractères alphabétiques, le comportement de comparaison numérique reste cohérent.

const enCollator = new Intl.Collator('en-US', { numeric: true });
const deCollator = new Intl.Collator('de-DE', { numeric: true });

const items = ['item1', 'item10', 'item2'];

console.log(items.sort(enCollator.compare));
// Output: ['item1', 'item2', 'item10']

console.log(items.sort(deCollator.compare));
// Output: ['item1', 'item2', 'item10']

Les deux locales produisent le même résultat car les chaînes contiennent uniquement des caractères ASCII et des nombres. Lorsque les chaînes incluent des caractères spécifiques à une locale, la comparaison alphabétique suit les règles de la locale tandis que la comparaison numérique reste cohérente.

Comparer des chaînes sans tri

Vous pouvez utiliser directement la méthode compare du collateur pour déterminer la relation entre deux chaînes sans trier un tableau entier.

const collator = new Intl.Collator('en-US', { numeric: true });

console.log(collator.compare('file2.txt', 'file10.txt'));
// Output: -1 (negative number means first argument comes before second)

console.log(collator.compare('file10.txt', 'file2.txt'));
// Output: 1 (positive number means first argument comes after second)

console.log(collator.compare('file2.txt', 'file2.txt'));
// Output: 0 (zero means arguments are equal)

Cela est utile lorsque vous devez vérifier l'ordre sans modifier un tableau, par exemple lors de l'insertion d'un élément dans une liste triée ou lors de la vérification qu'une valeur se situe dans une plage.

Comprendre la limitation avec les nombres décimaux

La collation numérique compare les séquences de chiffres, mais elle ne reconnaît pas les points décimaux comme faisant partie des nombres. Le caractère point est traité comme un séparateur, et non comme un séparateur décimal.

const collator = new Intl.Collator('en-US', { numeric: true });
const measurements = ['0.5', '0.05', '0.005'];

measurements.sort(collator.compare);

console.log(measurements);
// Output: ['0.005', '0.05', '0.5']

Le collateur traite chaque mesure comme trois composants numériques distincts : la partie avant le point, le point lui-même et la partie après le point. Il compare 0 avec 0 (égal), puis compare les parties après le point comme des nombres distincts : 5, 5 et 5 (égal). Ensuite, il compare la deuxième décimale : rien, 5 et rien. Cela produit un ordre incorrect pour les nombres décimaux.

Pour trier des nombres décimaux, convertissez-les en nombres réels et triez-les numériquement, ou utilisez un remplissage de chaîne pour garantir un ordre lexicographique correct.

Combiner la collation numérique avec d'autres options

L'option numeric fonctionne en parallèle avec d'autres options de collation comme sensitivity et caseFirst. Vous pouvez contrôler la façon dont le collateur gère la casse et les accents tout en maintenant le comportement de comparaison numérique.

const collator = new Intl.Collator('en-US', {
  numeric: true,
  sensitivity: 'base'
});

const items = ['Item1', 'item10', 'ITEM2'];

items.sort(collator.compare);

console.log(items);
// Output: ['Item1', 'ITEM2', 'item10']

L'option sensitivity: 'base' rend la comparaison insensible à la casse. Le collateur traite Item1, item1 et ITEM1 comme équivalents tout en comparant correctement les portions numériques.

Réutilisation des collators pour les performances

La création d'une nouvelle instance de Intl.Collator implique le chargement des données de locale et le traitement des options. Lorsque vous devez trier plusieurs tableaux ou effectuer de nombreuses comparaisons, créez le collator une seule fois et réutilisez-le.

const collator = new Intl.Collator('en-US', { numeric: true });

const files = ['file1.txt', 'file10.txt', 'file2.txt'];
const versions = ['1.2.10', '1.2.2', '1.10.5'];
const products = ['PROD-1', 'PROD-10', 'PROD-2'];

files.sort(collator.compare);
versions.sort(collator.compare);
products.sort(collator.compare);

Cette approche est plus efficace que de créer un nouveau collator pour chaque opération de tri. La différence de performance devient significative lors du tri de nombreux tableaux ou lors de comparaisons fréquentes.