Ordre naturel et tableau associatif

Un ordinateur et un homme ne voient pas toujours les choses de la même manière. Les tris notamment réservent des surprises. Prenons un exemple avec PHP.

Soit le tableau associatif suivant où la clé représente un numéro de version et la valeur, le nombre d’installation répertoriée pour celle-ci :

$tableau = array(   '2.1.1' => 38,   '1.0.8' => 14,   '2.1.12' => 53,   '2.1.6' => 38,   '1.9.7' => 26, );

On souhaite le trier selon la version. Pour cela, on peut utiliser la fonction ksort() qui trie un tableau selon la clé. On obtient alors :

array(5) {   ["1.0.8"] => int(14)   ["1.9.7"]=> int(26)   ["2.1.1"] => int(38)   ["2.1.12"]=> int(53)   ["2.1.6"]=> int(38) }

L’ordre semble bon à première vue mais si on regarde de plus près, les version 2.1.12 et 2.1.6 sont inversées. Tout du moins, notre cerveau les aurait triées différemment. La raison est toute simple. PHP constate que les clés sont des chaines de caractères et les trie donc comme telles. Notre cerveau quant à lui va un peu plus loin et comprend qu’on peut scinder cette chaine en 3 blocs et il va ordonner ces blocs et non la chaine elle-même Pour lui, le bon ordre est le suivant :

array(5) {   ["1.0.8"] => int(14)   ["1.9.7"]=> int(26)   ["2.1.1"] => int(38)   ["2.1.6"]=> int(38)   ["2.1.12"]=> int(53) }

On appelle cela l’ordre naturel.Malheureusement pour notre exemple, ce n’est pas la méthode de fonctionnement classique des ordinateurs. Il existe bien en PHP une fonction natsort() de tri par ordre naturel mais elle ne sait trier que par valeur et non par clé. Certains petits malins pourraient être tentés d’utiliser la fonction array_flip(), qui inverse les clés et les valeurs d’un tableau, avant et après le tri pour contourner ce problème mais voici le tableau qu’ils obtiendraient :

array(4) {   ["1.0.8"]=> int(14)   ["1.9.7"]=>  int(26)   ["2.1.6"]=>  int(38)   ["2.1.12"]=>  int(53) }

La version 2.1.1 a disparu. La raison est toute simple, en inversant les clés et les valeurs, les versions 2.1.1 et 2.1.6 ont la même clé (38), la seconde version écrase donc la première.

Heureusement avec un peu d’astuce, il est possible de trier ce tableau dans l’ordre naturel sans risquer cet effet de bord en combinant les fonctions uksort() et strnatcmp() ainsi :

uksort($tableau, 'strnatcmp');

On obtient bien :

array(5) {   ["1.0.8"] => int(14)   ["1.9.7"]=> int(26)   ["2.1.1"] => int(38)   ["2.1.6"]=> int(38)   ["2.1.12"]=> int(53) }

La subtilité est que la fonction uksort() demande un callback vers une fonction qui va être chargée de trier les éléments du tableau 2 à 2. Cette fonction de comparaison doit retourner un entier supérieur, égal ou inférieur à zéro, pour, respectivement, indiquer que le premier argument est supérieur, égal ou inférieur au second.

Si généralement on utilise une fonction « maison » avec un callback, il est parfaitement possible d’utiliser une fonction native de PHP. C’est ce qui est fait ici afin de ne pas réinventer la roue et d’optimiser les performances.

Ce contenu a été publié dans PHP. Vous pouvez le mettre en favoris avec ce permalien.

6 réponses à Ordre naturel et tableau associatif

  1. Thanh dit :

    Je ne connaissais pas strnatcmp(). Merci pour l’info.

  2. Seza dit :

    Excellente info , je ne connaissais pas non plus. J’avais déjà eu besoin de faire des tris en ordre naturel sur des références produit REF_1 etc …

    Là dessus j’avais trouvé une astuce pour les triées dont j’était assez fier mais alors là c’est royal. Merci !

    PS : mon astuce consistait à récupéré la partie numérique et en fonction de la longueur du chiffre, mettre la lettre de l’alphabet respective à la longueur devant le chiffre. REF_1 devenait REF_A1, REF_10 devenait REF_B10 etc…

  3. fredmac dit :

    Bonjour,
    En fait ksort ce comporte comme lorsque nous apprenions à effectuer des opérations sur des chiffres comportant des unités différentes (mètre – centimètre – millimètre).

    Dans ce cas 1,6 centimètre est plus grand que 1,12 centimètre : pour comparaison on ajoute un zéro pour faire correspondance . Soit 1,60 cm > 1,25 cm
    C
    ‘est le principe de matrice qui est à l’œuvre ; le zero est ajouté dérrière (après le 6 dans notre exemple = 1,60 cm) et non devant (ce qui nous donnerais 1,06 cm).

    Peut être au la plupart on oublié ce principe que l’on nous apprend très trop à l’école.

    Merci pour l’astuce, ça m’évitera de me prendre la tête les prochaines fois…

  4. fredmac dit :

    Trop de fautes d’orthographes…. désolé

  5. Lyl dit :

    Un exemple pour illustrer ton billet (Dslée pour la pub…) http://www.ikea.com/webapp/wcs/s... (dans le menu déroulant pour le choix des dimensions)
    Après moi ton truc je l’utiliserai jamais, mais bonne continuation 🙂

  6. Domi dit :

    Chapeau bas.
    Je vais ranger cet article dqns ma "base de connaissances".

Les commentaires sont fermés.