Dans la continuité du billet très intéressant de Vincent Battaglia consacré à l'utilité du système de numération hexatridécimal (soit en base 36) dans l'algorithme d'un raccourcisseur d'URL, j'ai voulu savoir si utiliser d'autres systèmes de numération encore plus vastes, comme le base 62 évoqué à la fin de son article, pouvait se faire avec autant de facilité, ce qui permettrait de générer des URL encore plus courtes à moindre effort.
Comme il l'a déjà bien expliqué, le principal critère qui influencera le choix entre l'une de ces deux bases est certainement le souci de la sensibilité à la casse. En effet, la base 36 se concentre sur le système alphanumérique minuscule tandis que la base 62 inclut aussi les caractères majuscules.
D'autre part, il va évidemment de soi qu'au plus la base est élevée (autrement dit au plus l'éventail de symboles possibles est vaste), au plus un nombre peut-être représenté de manière concise. Dans le cadre des raccourcisseurs d'URL, c'est donc aussi un critère primordial. Non seulement pour l'économie de caractères que cela implique, mais aussi pour le nombre d'identifiants que le service pourra gérer, et donc le nombre d'URL qu'il pourra prétendre offrir. Bien que la plupart des services actuels comme TinyURL, bit.ly ou goo.gl semblent s'être limités à une base 62, d'autres sont plus aventureux, comme par exemple le service (belge, disons-le !) http://ui.tl qui affirme être le plus concis du marché par l'utilisation d'une base 163, prenant ainsi le risque d'inclure des caractères accentués dans ses résultats.
Lors de la mise en pratique, je fus tout d'abord surpris de découvrir que la fonction base_convert de PHP ne supporte pas la conversion d'un nombre en base 62. L'occasion étant trop tentante, j'en ai profité pour réécrire une fonction sensiblement identique permettant cette fois de jongler entre différentes bases arbitraires avec une totale liberté.
Par la même occasion, j'y ai ajouté au travers d'un dernier paramètre optionnel la possibilité de spécifier le jeu de caractères à prendre en compte lors de la conversion. J'y vois plusieurs cas d'utilisation concrets, comme par exemple :
- Pouvoir déjouer le caractère prédictif inhérent à ce genre de conversion, en spécifiant un alphabet dont l'ordre aura été préalablement altéré.
- Pouvoir générer des valeurs "user-friendly", en omettant par exemple les caractères susceptibles de prêter à confusion lors de la lecture, tels o,O,0,1,l, etc...
- Pouvoir définir son propre jeu de caractères, qui pourrait par exemple inclure des caractères spéciaux.
/** * Convertit un nombre entre différentes bases. * * @param string $number Le nombre à convertir * @param int $frombase La base du nombre * @param int $tobase La base dans laquelle on doit le convertir * @param string $map Eventuellement, l'alphabet à utiliser * @return string|false Le nombre converti ou FALSE en cas d'erreur * @author Geoffray Warnants */ function base_to($number, $frombase, $tobase, $map=false) { if ($frombase<2 || ($tobase==0 && ($tobase=strlen($map))<2) || $tobase<2) { return false; } if (!$map) { $map = implode('',array_merge(range(0,9),range('a','z'),range('A','Z'))); } // conversion en base 10 si nécessaire if ($frombase != 10) { $number = ($frombase <= 16) ? strtolower($number) : (string)$number; $map_base = substr($map,0,$frombase); $decimal = 0; for ($i=0, $n=strlen($number); $i<$n; $i++) { $decimal += strpos($map_base,$number[$i]) * pow($frombase,($n-$i-1)); } } else { $decimal = $number; } // conversion en $tobase si nécessaire if ($tobase != 10) { $map_base = substr($map,0,$tobase); $tobase = strlen($map_base); $result = ''; while ($decimal >= $tobase) { $result = $map_base[$decimal%$tobase].$result; $decimal /= $tobase; } return $map_base[$decimal].$result; } return $decimal; }
Pour se faire une idée de l'allure que peuvent avoir les valeurs retournées par cette fonction, voici à titre indicatif les résultats d'une série de conversions entre différentes bases. En ce qui concerne la base 163, j'ai considéré le jeu de caractères revendiqué par le service http://ui.tl.
base 10 | base 16 | base 36 | base 62 | base 163 |
---|---|---|---|---|
100 | 64 | 2s | 1C | Æ |
1000 | 3e8 | rs | g8 | 'Ú |
10000 | 2710 | 7ps | 2Bi | e% |
100000 | 186a0 | 255s | q0U | %}I |
1000000 | f4240 | lfls | 4c92 | OIÌ |
10000000 | 989680 | 5yc1s | FXsk | $6Ô@ |
100000000 | 5f5e100 | 1njchs | 6LAze | @gÉ1 |
1000000000 | 3b9aca00 | gjdgxs | 15FTGg | #5Û#. |
10000000000 | 2540be400 | 4ldqpdk | aUKYOs | 6^/5Ç |
Vos commentaires
j'ai juste une question, pourquoi lorsque que je fais un base_to de quelque chose en faisant l'inverse ça redonne pas pareil?
exemple:
echo base_to('musique', 32, 62); // qUOmj
echo base_to('qUOmj', 62, 32); // br9j0f
En effet, les noms de domaines sont case-insensitive, ou si je me trompe, ont toujours été définis en minuscule. Donc une partie case-insensitive suivie d'une autre case-sensitive, je trouve ça 'illogique'
S.