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 2N
número inteiro de bits se as entradas forem N
números inteiros de dois bits. Ou seja, se minhas entradas são 16
nú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 de32
nú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 16
nú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 a
e b
tem 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 16
número de bit assinado , a saída está dentro dos limites de um 32
nú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 2N
número inteiro assinado, usei o 4N
tipo inteiro (a última divisão por 2
traz 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 !!