La fonction de couplage de Cantor est vraiment l'une des meilleures là-bas compte tenu de sa simplicité, de sa rapidité et de son faible encombrement, mais il y a quelque chose de mieux publié à Wolfram par Matthew Szudzik, ici . La limitation de la fonction d'appariement de Cantor (relativement) est que la plage de résultats codés ne reste pas toujours dans les limites d'un 2N
entier de bits si les entrées sont N
des entiers de deux bits. Autrement dit, si mes entrées sont 16
des entiers de deux bits allant de 0 to 2^16 -1
, alors il y a des 2^16 * (2^16 -1)
combinaisons d'entrées possibles, donc par le principe évident de Pigeonhole , nous avons besoin d'une sortie de taille au moins 2^16 * (2^16 -1)
, qui est égale 2^32 - 2^16
, ou en d'autres termes, une carte de32
les nombres de bits devraient être réalisables idéalement. Cela peut ne pas être de peu d'importance pratique dans le monde de la programmation.
Fonction d'appariement Cantor :
(a + b) * (a + b + 1) / 2 + a; where a, b >= 0
Le mappage pour deux nombres entiers maximum de 16 bits (65535, 65535) sera 8589803520 qui, comme vous le voyez, ne peut pas tenir sur 32 bits.
Entrez dans la fonction de Szudzik :
a >= b ? a * a + a + b : a + b * b; where a, b >= 0
Le mappage pour (65535, 65535) sera désormais 4294967295 qui, comme vous le voyez, est un entier de 32 bits (0 à 2 ^ 32 -1). C'est là que cette solution est idéale, elle utilise simplement chaque point de cet espace, donc rien ne peut être plus efficace en termes d'espace.
Considérant maintenant le fait que nous traitons généralement les implémentations signées de nombres de différentes tailles dans les langages / frameworks, considérons signed 16
les entiers binaires allant de -(2^15) to 2^15 -1
(plus tard, nous verrons comment étendre même la sortie pour s'étendre sur la plage signée). Depuis a
et b
doivent être positifs, ils vont de 0 to 2^15 - 1
.
Fonction d'appariement Cantor :
Le mappage pour deux nombres entiers signés maximum 16 bits (32767, 32767) sera 2147418112, ce qui est juste en deçà de la valeur maximale pour un entier signé 32 bits.
Maintenant la fonction de Szudzik :
(32767, 32767) => 1073741823, beaucoup plus petit ..
Prenons les entiers négatifs. C'est au-delà de la question d'origine que je connais, mais juste pour élaborer pour aider les futurs visiteurs.
Fonction d'appariement Cantor :
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
(A + B) * (A + B + 1) / 2 + A;
(-32768, -32768) => 8589803520 qui est Int64. La sortie 64 bits pour les entrées 16 bits peut être tellement impardonnable !!
La fonction de Szudzik :
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
A >= B ? A * A + A + B : A + B * B;
(-32768, -32768) => 4294967295 qui est 32 bits pour la plage non signée ou 64 bits pour la plage signée, mais encore mieux.
Maintenant, tout cela alors que la sortie a toujours été positive. Dans le monde signé, ce sera encore plus d'économie d'espace si nous pouvions transférer la moitié de la sortie sur l'axe négatif . Vous pouvez le faire comme ceci pour Szudzik:
A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
C = (A >= B ? A * A + A + B : A + B * B) / 2;
a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
(-32768, 32767) => -2147483648
(32767, -32768) => -2147450880
(0, 0) => 0
(32767, 32767) => 2147418112
(-32768, -32768) => 2147483647
Ce que je fais: Après avoir appliqué un poids de 2
aux entrées et avoir parcouru la fonction, je divise ensuite la sortie par deux et amène certaines d'entre elles à l'axe négatif en multipliant par -1
.
Voir les résultats, pour toute entrée dans la plage d'un 16
nombre de bits signé , la sortie se situe dans les limites d'un 32
entier de bit signé qui est cool. Je ne sais pas comment procéder de la même manière pour la fonction d'appariement Cantor, mais je n'ai pas essayé autant que ce n'est pas aussi efficace. De plus, plus de calculs impliqués dans la fonction d'appariement de Cantor signifient qu'elle est aussi plus lente .
Voici une implémentation C #.
public static long PerfectlyHashThem(int a, int b)
{
var A = (ulong)(a >= 0 ? 2 * (long)a : -2 * (long)a - 1);
var B = (ulong)(b >= 0 ? 2 * (long)b : -2 * (long)b - 1);
var C = (long)((A >= B ? A * A + A + B : A + B * B) / 2);
return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}
public static int PerfectlyHashThem(short a, short b)
{
var A = (uint)(a >= 0 ? 2 * a : -2 * a - 1);
var B = (uint)(b >= 0 ? 2 * b : -2 * b - 1);
var C = (int)((A >= B ? A * A + A + B : A + B * B) / 2);
return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}
Étant donné que les calculs intermédiaires peuvent dépasser les limites de l' 2N
entier signé, j'ai utilisé le 4N
type entier (la dernière division par 2
ramène le résultat à 2N
).
Le lien que j'ai fourni sur une solution alternative représente bien un graphique de la fonction utilisant chaque point dans l'espace. C'est incroyable de voir que vous pouvez coder de manière unique une paire de coordonnées en un seul numéro de manière réversible! Monde magique des nombres !!