La función de emparejamiento de Cantor es realmente una de las mejores, considerando que es simple, rápida y eficiente en cuanto al espacio, pero hay algo aún mejor publicado en Wolfram por Matthew Szudzik, aquí . La limitación de la función de emparejamiento de Cantor (relativamente) es que el rango de resultados codificados no siempre se mantiene dentro de los límites de un 2N
número entero de bits si las entradas son N
números enteros de dos bits. Es decir, si mis entradas son 16
enteros de dos bits que van desde 0 to 2^16 -1
, entonces hay 2^16 * (2^16 -1)
combinaciones de entradas posibles, por lo que, según el Principio de Pigeonhole obvio , necesitamos una salida de tamaño al menos 2^16 * (2^16 -1)
, que sea igual 2^32 - 2^16
o, en otras palabras, un mapa de32
los números de bits deberían ser factibles idealmente. Esto puede no ser de poca importancia práctica en el mundo de la programación.
Función de emparejamiento de Cantor :
(a + b) * (a + b + 1) / 2 + a; where a, b >= 0
La asignación para dos enteros máximos de 16 bits (65535, 65535) será 8589803520 que, como puede ver, no puede caber en 32 bits.
Ingrese la función de Szudzik :
a >= b ? a * a + a + b : a + b * b; where a, b >= 0
La asignación para (65535, 65535) ahora será 4294967295 que, como puede ver, es un entero de 32 bits (0 a 2 ^ 32 -1). Aquí es donde esta solución es ideal, simplemente utiliza cada punto en ese espacio, por lo que nada puede ser más eficiente.
Ahora, considerando el hecho de que generalmente tratamos con implementaciones firmadas de números de varios tamaños en lenguajes / marcos, consideremos signed 16
los enteros de bits que van desde -(2^15) to 2^15 -1
(más adelante veremos cómo extender incluso la salida para que se extienda sobre el rango firmado). Desde a
y b
tienen que ser positivos van desde 0 to 2^15 - 1
.
Función de emparejamiento de Cantor :
La asignación para dos números enteros máximos con signo de 16 bits (32767, 32767) será 2147418112, que es justo por debajo del valor máximo para el número entero con signo de 32 bits.
Ahora la función de Szudzik :
(32767, 32767) => 1073741823, mucho más pequeño ...
Consideremos los enteros negativos. Eso está más allá de la pregunta original que conozco, pero solo se elabora para ayudar a los futuros visitantes.
Función de emparejamiento de 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 que es Int64. ¡La salida de 64 bits para entradas de 16 bits puede ser tan imperdonable!
La función 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, que es 32 bits para rango sin signo o 64 bits para rango con signo, pero aún mejor.
Ahora todo esto mientras que la salida siempre ha sido positiva. En el mundo firmado, se ahorrará aún más espacio si pudiéramos transferir la mitad de la salida al eje negativo . Podrías hacerlo así para Szudzik's:
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
Lo que hago: después de aplicar un peso de 2
a las entradas y pasar por la función, divido la salida por dos y llevo algunas de ellas al eje negativo multiplicando por -1
.
Vea los resultados, para cualquier entrada en el rango de un 16
número de bit con signo , la salida se encuentra dentro de los límites de un 32
entero de bit con signo que es genial. No estoy seguro de cómo hacer lo mismo para la función de emparejamiento de Cantor, pero no intenté tanto como no es tan eficiente. Además, más cálculos involucrados en la función de emparejamiento de Cantor significa que también es más lento .
Aquí hay una implementación de 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;
}
Como los cálculos intermedios pueden exceder los límites del 2N
entero con signo, he usado el 4N
tipo entero (la última división 2
devuelve el resultado 2N
).
El enlace que he proporcionado en una solución alternativa muestra muy bien un gráfico de la función que utiliza cada punto en el espacio. ¡Es sorprendente ver que puedes codificar de forma única un par de coordenadas en un solo número de forma reversible! ¡Mundo mágico de números!