La funzione di accoppiamento Cantor è davvero una delle migliori là fuori considerando la sua semplicità, rapidità ed efficienza dello spazio, ma c'è qualcosa di ancora meglio pubblicato su Wolfram da Matthew Szudzik, qui . Il limite della funzione di accoppiamento Cantor (relativamente) è che l'intervallo dei risultati codificati non rimane sempre entro i limiti di un 2N
numero intero di bit se gli ingressi sono N
numeri interi di due bit. Cioè, se i miei input sono 16
numeri interi a due bit che vanno da 0 to 2^16 -1
allora, allora ci sono 2^16 * (2^16 -1)
combinazioni di input possibili, quindi dall'ovvio Principio di Pigeonhole , abbiamo bisogno di un output di dimensioni almeno numeri di bit dovrebbero essere idealmente fattibili. Questo potrebbe non avere poca importanza pratica nel mondo della programmazione.2^16 * (2^16 -1)
, che è uguale 2^32 - 2^16
o, in altre parole, una mappa di32
Funzione di abbinamento Cantor :
(a + b) * (a + b + 1) / 2 + a; where a, b >= 0
La mappatura per due numeri massimi al massimo di 16 bit (65535, 65535) sarà 8589803520 che, come vedi, non può essere adattato a 32 bit.
Inserisci la funzione di Szudzik :
a >= b ? a * a + a + b : a + b * b; where a, b >= 0
Il mapping per (65535, 65535) sarà ora 4294967295 che, come vedi, è un numero intero a 32 bit (da 0 a 2 ^ 32 -1). È qui che questa soluzione è ideale, utilizza semplicemente ogni singolo punto in quello spazio, quindi nulla può ottenere uno spazio più efficiente.
Ora considerando il fatto che in genere trattiamo le implementazioni firmate di numeri di varie dimensioni in linguaggi / framework, consideriamo gli signed 16
interi di bit che vanno da -(2^15) to 2^15 -1
(in seguito vedremo come estendere anche l'output per estendere l'intervallo firmato). Dal momento che a
e b
devono essere positivi vanno da 0 to 2^15 - 1
.
Funzione di abbinamento Cantor :
La mappatura per due numeri interi con segno massimo a 16 bit (32767, 32767) sarà 2147418112 che è appena al di sotto del valore massimo per numero intero a 32 bit con segno.
Ora la funzione di Szudzik :
(32767, 32767) => 1073741823, molto più piccolo ..
Consideriamo gli interi negativi. Questo è al di là della domanda originale che conosco, ma solo elaborando per aiutare i futuri visitatori.
Funzione di abbinamento 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 che è Int64. L'uscita a 64 bit per gli ingressi a 16 bit potrebbe essere così imperdonabile !!
La funzione di 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 che è 32 bit per l'intervallo senza segno o 64 bit per l'intervallo con segno, ma ancora meglio.
Ora tutto questo mentre l'output è sempre stato positivo. Nel mondo firmato, sarà ancora più salvaspazio se potessimo trasferire metà dell'output sull'asse negativo . Potresti farlo in questo modo per 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
Cosa faccio: dopo aver applicato un peso 2
sugli ingressi e aver esaminato la funzione, allora divido l'output per due e ne porto alcuni sull'asse negativo moltiplicando per -1
.
Vedere i risultati, per qualsiasi input nell'intervallo di un 16
numero di bit con segno, l'output rientra nei limiti di un segno32
numero intero di bit con segno che è interessante. Non sono sicuro di come procedere allo stesso modo per la funzione di abbinamento Cantor ma non ho provato tanto quanto non è efficiente. Inoltre, un numero maggiore di calcoli coinvolti nella funzione di accoppiamento Cantor significa anche che è più lento .
Ecco un'implementazione in 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;
}
Poiché i calcoli intermedi possono superare i limiti di 2N
numeri interi con segno, ho usato il 4N
tipo intero (l'ultima divisione per2
riporta il risultato a 2N
).
Il collegamento che ho fornito su una soluzione alternativa descrive bene un grafico della funzione che utilizza ogni singolo punto nello spazio. È incredibile vedere che potresti codificare in modo univoco una coppia di coordinate in un singolo numero in modo reversibile! Magico mondo dei numeri !!