A função de emparelhamento Cantor é realmente uma das melhores por aí, considerando sua simplicidade, rapidez e eficiência de espaço, mas há algo ainda melhor publicado na Wolfram por Matthew Szudzik, aqui . A limitação da função de emparelhamento do Cantor (relativamente) é que o intervalo de resultados codificados nem sempre fica dentro dos limites de um 2Nnúmero inteiro de bits se as entradas forem Nnúmeros inteiros de dois bits. Ou seja, se minhas entradas são 16números inteiros de dois bits que variam de 0 to 2^16 -1, então existem 2^16 * (2^16 -1)combinações de entradas possíveis; portanto, pelo óbvio Princípio Pigeonhole , precisamos de uma saída de tamanho, pelo menos 2^16 * (2^16 -1), igual a 2^32 - 2^16, ou seja, um mapa de32números de bits devem ser viáveis idealmente. Isso pode não ter pouca importância prática no mundo da programação.
Função de emparelhamento Cantor :
(a + b) * (a + b + 1) / 2 + a; where a, b >= 0
O mapeamento para dois números inteiros máximos de 16 bits (65535, 65535) será 8589803520 que, como você vê, não pode caber em 32 bits.
Entre na função de Szudzik :
a >= b ? a * a + a + b : a + b * b; where a, b >= 0
O mapeamento para (65535, 65535) agora será 4294967295 que, como você vê, é um número inteiro de 32 bits (0 a 2 ^ 32 -1). É aqui que esta solução é ideal, ela simplesmente utiliza todos os pontos desse espaço, para que nada possa ser mais eficiente em termos de espaço.
Agora, considerando o fato de que normalmente lidamos com implementações assinadas de números de vários tamanhos em idiomas / estruturas, vamos considerar signed 16números inteiros de bits que variam de -(2^15) to 2^15 -1(mais tarde veremos como estender até a saída para abranger o intervalo assinado). Desde ae btem que ser positivo eles variam de 0 to 2^15 - 1.
Função de emparelhamento Cantor :
O mapeamento para dois números inteiros máximos assinados com mais de 16 bits (32767, 32767) será 2147418112, que está um pouco abaixo do valor máximo para o número inteiro assinado de 32 bits.
Agora a função de Szudzik :
(32767, 32767) => 1073741823, muito menor ..
Vamos considerar números inteiros negativos. Isso está além da pergunta original que eu conheço, mas apenas elaborando para ajudar futuros visitantes.
Função de emparelhamento 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 é Int64. A saída de 64 bits para entradas de 16 bits pode ser tão imperdoável!
Função 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 é de 32 bits para o intervalo não assinado ou de 64 bits para o intervalo assinado, mas ainda melhor.
Agora, tudo isso enquanto a saída sempre foi positiva. No mundo assinado, economizará ainda mais espaço se pudermos transferir metade da saída para o eixo negativo . Você poderia fazer isso para o 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
O que eu faço: Depois de aplicar um peso de 2às entradas e passar pela função, divido a saída por duas e levo algumas delas para o eixo negativo multiplicando por -1.
Veja os resultados, para qualquer entrada no intervalo de um 16número de bit assinado , a saída está dentro dos limites de um 32número inteiro de bits assinado, o que é legal. Não sei como proceder da mesma maneira para a função de emparelhamento Cantor, mas não tentei tanto quanto não é tão eficiente. Além disso, mais cálculos envolvidos na função de emparelhamento Cantor também são mais lentos .
Aqui está uma implementação em 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 os cálculos intermediários podem exceder os limites do 2Nnúmero inteiro assinado, usei o 4Ntipo inteiro (a última divisão por 2traz de volta o resultado para 2N).
O link que forneci em uma solução alternativa mostra um gráfico da função utilizando cada ponto no espaço. É incrível ver que você pode codificar exclusivamente um par de coordenadas para um único número reversível! Mundo mágico dos números !!