mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-09-26 03:08:41 +02:00
implement and test an n-pair pairing function for unique ID generation
This commit is contained in:
parent
58315a56b2
commit
52d216b0f7
111
navigator/src/functions/szudzikPair.js
Normal file
111
navigator/src/functions/szudzikPair.js
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Perform isqrt on BigInt
|
||||
*
|
||||
* @param {BigInt} s - Square
|
||||
* @returns {BigInt} - Integer square root
|
||||
*/
|
||||
const bigIntSqrt = (s) => {
|
||||
if (s < 0n)
|
||||
throw new Error("isqrt of negative number is not allowed");
|
||||
|
||||
if (s < 2)
|
||||
return s;
|
||||
|
||||
if (s <= Number.MAX_SAFE_INTEGER)
|
||||
return BigInt(Math.floor(Math.sqrt(Number(s))));
|
||||
|
||||
let x0, x1;
|
||||
x0 = s / 2n; // initial estimate
|
||||
|
||||
x1 = (x0 + s / x0) / 2n;
|
||||
while (x1 < x0) {
|
||||
x0 = x1;
|
||||
x1 = (x0 + s / x0) / 2n;
|
||||
}
|
||||
return x0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an unsigned 32 bit hash of a string
|
||||
*
|
||||
* @param {String} string - string to hash
|
||||
* @returns {Number} - hash of string
|
||||
*/
|
||||
const hashString = (string) => {
|
||||
let i = 0, digest = 0, char = 0, length = string.length;
|
||||
for (; i < length; i++) {
|
||||
char = string.charCodeAt(i);
|
||||
digest = ((digest * 31) + char) | 0;
|
||||
}
|
||||
if (digest < 0)
|
||||
digest += 2**32
|
||||
return digest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode two BigInt values to one unique BigInt value
|
||||
*
|
||||
* @param {BigInt} k0 - first pair element
|
||||
* @param {BigInt} k1 - second pair element
|
||||
* @returns {BigInt} - The encoded result
|
||||
*/
|
||||
const szudzikPair2 = (k0, k1) =>
|
||||
k0 > k1
|
||||
? k0 ** 2n + k1
|
||||
: k1 ** 2n + k1 + k0
|
||||
|
||||
/**
|
||||
* Decode one unique BigInt value to its original pair
|
||||
*
|
||||
* @param {BigInt} z - Encoded value
|
||||
* @returns {BigInt[]} - Decoded pair
|
||||
*/
|
||||
const szudzikUnpair2 = (z) => {
|
||||
const r = bigIntSqrt(z);
|
||||
return (z - r ** 2n) < r
|
||||
? [r, z - r ** 2n]
|
||||
: [z - r ** 2n - r, r];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an arbitrary number of BigInt values to one unique BigInt value
|
||||
*
|
||||
* @param {...BigInt|BigInt[]} args - tuple to encode
|
||||
* @returns {BigInt} - Encoded value
|
||||
*/
|
||||
function szudzikPair(...args) {
|
||||
let k0, k1;
|
||||
const k = [...(Array.isArray(args[0]) ? args[0] : args)].map(v => typeof v === 'string' ? BigInt(hashString(v)) : BigInt(v));
|
||||
if (k.length == 2)
|
||||
return szudzikPair2(...k);
|
||||
while (k.length >= 2) {
|
||||
k0 = k.shift();
|
||||
k1 = k[0];
|
||||
k[0] = szudzikPair2(k0, k1);
|
||||
}
|
||||
return k[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode one unique BigInt value to its original tuple of length n
|
||||
*
|
||||
* @param {BigInt} z - Encoded value
|
||||
* @param {Number} n - Size of decoded tuple
|
||||
* @returns {BigInt[]} - Decoded tuple
|
||||
*/
|
||||
function szudzikUnpair(z, n = 2) {
|
||||
const k = [BigInt(z)];
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
k.unshift(...szudzikUnpair2(k.shift()));
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
export {
|
||||
szudzikPair,
|
||||
szudzikUnpair,
|
||||
szudzikPair2,
|
||||
szudzikUnpair2,
|
||||
bigIntSqrt,
|
||||
hashString,
|
||||
}
|
139
navigator/src/functions/szudzikPair.test.js
Normal file
139
navigator/src/functions/szudzikPair.test.js
Normal file
@ -0,0 +1,139 @@
|
||||
import {
|
||||
szudzikPair,
|
||||
szudzikUnpair,
|
||||
szudzikPair2,
|
||||
szudzikUnpair2,
|
||||
bigIntSqrt,
|
||||
hashString,
|
||||
} from "./szudzikPair";
|
||||
|
||||
describe('Szudzik Pairing', () => {
|
||||
describe('Internal functions', () => {
|
||||
describe('bigIntSqrt', () => {
|
||||
it('can accurately calculate a small square root', () => {
|
||||
expect(bigIntSqrt(25n)).toBe(5n);
|
||||
expect(bigIntSqrt(100n)).toBe(10n);
|
||||
expect(bigIntSqrt(16n)).toBe(4n);
|
||||
});
|
||||
|
||||
it('can match Math.floor(Math.sqrt())', () => {
|
||||
const inputs = [...Array(1000).keys()];
|
||||
for (const input of inputs) {
|
||||
expect(bigIntSqrt(BigInt(input)) == Math.floor(Math.sqrt(input))).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('can calculate very large square roots', () => {
|
||||
const inputs = [...Array(1000).keys()].map(BigInt).map(x => x + BigInt(Number.MAX_SAFE_INTEGER.toString(10)));
|
||||
for (const input of inputs) {
|
||||
const s = input ** 2n;
|
||||
expect(bigIntSqrt(s)).toBe(input);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
describe('hashString', () => {
|
||||
it('can generate positive unique hashes for host names', () => {
|
||||
const hosts = [
|
||||
'localhost',
|
||||
'osd1',
|
||||
'osd2',
|
||||
'osd3',
|
||||
'fsgw1',
|
||||
'fsgw2',
|
||||
'fsgw3',
|
||||
'ubuntu',
|
||||
'rocky',
|
||||
'server',
|
||||
'storinator',
|
||||
'bartholomew',
|
||||
];
|
||||
const hashes = hosts.map(host => hashString(host));
|
||||
expect((new Set(hashes)).size).toEqual(hosts.length);
|
||||
expect(hashes.every(h => h >= 0)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('szudzikPair2', () => {
|
||||
it('can generate an encoding from two numbers', () => {
|
||||
expect(szudzikPair2(1n, 2n)).toBe(7n);
|
||||
expect(szudzikPair2(2n, 1n)).toBe(5n);
|
||||
expect(szudzikPair2(10n, 25n)).toBe(660n);
|
||||
});
|
||||
|
||||
it('can generate an encoding from two > MAX_SAFE_INT numbers', () => {
|
||||
expect(szudzikPair2(BigInt(Number.MAX_SAFE_INTEGER) + 5n, BigInt(Number.MAX_SAFE_INTEGER) + 10n)).toBe(81129638414606861839774099963998n);
|
||||
});
|
||||
});
|
||||
|
||||
describe('szudzikUnpair2', () => {
|
||||
it('can decode into the original pair', () => {
|
||||
expect(szudzikUnpair2(7n)).toEqual([1n, 2n]);
|
||||
expect(szudzikUnpair2(5n)).toEqual([2n, 1n]);
|
||||
expect(szudzikUnpair2(660n)).toEqual([10n, 25n]);
|
||||
});
|
||||
|
||||
it('can decode into the original pair for > MAX_SAFE_INT', () => {
|
||||
expect(szudzikUnpair2(81129638414606861839774099963998n)).toEqual([BigInt(Number.MAX_SAFE_INTEGER) + 5n, BigInt(Number.MAX_SAFE_INTEGER) + 10n]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('API', () => {
|
||||
describe('szudzikPair', () => {
|
||||
it('can generate an encoding from two numbers', () => {
|
||||
expect(szudzikPair(1n, 2n)).toBe(7n);
|
||||
expect(szudzikPair(2n, 1n)).toBe(5n);
|
||||
expect(szudzikPair(10n, 25n)).toBe(660n);
|
||||
});
|
||||
|
||||
it('can generate an encoding from two > MAX_SAFE_INT numbers', () => {
|
||||
expect(szudzikPair(BigInt(Number.MAX_SAFE_INTEGER) + 5n, BigInt(Number.MAX_SAFE_INTEGER) + 10n)).toBe(81129638414606861839774099963998n);
|
||||
});
|
||||
});
|
||||
|
||||
describe('szudzikUnpair', () => {
|
||||
it('can decode into the original pair', () => {
|
||||
expect(szudzikUnpair(7n, 2)).toEqual([1n, 2n]);
|
||||
expect(szudzikUnpair(5n, 2)).toEqual([2n, 1n]);
|
||||
expect(szudzikUnpair(660n, 2)).toEqual([10n, 25n]);
|
||||
});
|
||||
|
||||
it('can decode into the original pair for > MAX_SAFE_INT', () => {
|
||||
expect(szudzikUnpair(81129638414606861839774099963998n, 2)).toEqual([BigInt(Number.MAX_SAFE_INTEGER) + 5n, BigInt(Number.MAX_SAFE_INTEGER) + 10n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('works with n > 2 (n = 4, 20^4 tuples)', () => {
|
||||
const i = [...Array(20).keys()].map(BigInt);
|
||||
const j = [...i];
|
||||
const k = [...i];
|
||||
const l = [...i];
|
||||
const tuples = [];
|
||||
for (let i0 of i) {
|
||||
for (let j0 of j) {
|
||||
for (let k0 of k) {
|
||||
for (let l0 of l) {
|
||||
tuples.push([l0, k0, j0, i0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const encodings = tuples.map(szudzikPair);
|
||||
|
||||
describe('has no collisions', () => {
|
||||
expect((new Set(encodings)).size).toEqual(encodings.length);
|
||||
});
|
||||
|
||||
describe('can be decoded', () => {
|
||||
const decodings = encodings.map(e => szudzikUnpair(e, 4));
|
||||
expect(decodings).toEqual(tuples);
|
||||
});
|
||||
});
|
||||
|
||||
describe('works with array or nargs', () => {
|
||||
const input = [1n, 2n, 3n, 4n];
|
||||
expect(szudzikPair(input)).toEqual(szudzikPair(...input));
|
||||
});
|
||||
})
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user