You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Offset the alphabet by 1
This commit is contained in:
@@ -285,26 +285,49 @@ describe("utils", function() {
|
|||||||
|
|
||||||
describe('baseToString', () => {
|
describe('baseToString', () => {
|
||||||
it('should calculate the appropriate string from numbers', () => {
|
it('should calculate the appropriate string from numbers', () => {
|
||||||
expect(baseToString(BigInt(10))).toEqual(DEFAULT_ALPHABET[10]);
|
// Verify the whole alphabet
|
||||||
expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual('k');
|
for (let i = BigInt(1); i <= DEFAULT_ALPHABET.length; i++) {
|
||||||
expect(baseToString(BigInt(6241))).toEqual("ab");
|
console.log({i}); // for debugging
|
||||||
expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual('cb');
|
expect(baseToString(i)).toEqual(DEFAULT_ALPHABET[Number(i) - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just quickly double check that repeated characters aren't treated as padding, particularly
|
||||||
|
// at the beginning of the alphabet where they are most vulnerable to this behaviour.
|
||||||
|
expect(baseToString(BigInt(1))).toEqual(DEFAULT_ALPHABET[0].repeat(1));
|
||||||
|
expect(baseToString(BigInt(96))).toEqual(DEFAULT_ALPHABET[0].repeat(2));
|
||||||
|
expect(baseToString(BigInt(9121))).toEqual(DEFAULT_ALPHABET[0].repeat(3));
|
||||||
|
expect(baseToString(BigInt(866496))).toEqual(DEFAULT_ALPHABET[0].repeat(4));
|
||||||
|
expect(baseToString(BigInt(82317121))).toEqual(DEFAULT_ALPHABET[0].repeat(5));
|
||||||
|
expect(baseToString(BigInt(7820126496))).toEqual(DEFAULT_ALPHABET[0].repeat(6));
|
||||||
|
|
||||||
|
expect(baseToString(BigInt(10))).toEqual(DEFAULT_ALPHABET[9]);
|
||||||
|
expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual('j');
|
||||||
|
expect(baseToString(BigInt(6337))).toEqual("ab");
|
||||||
|
expect(baseToString(BigInt(80), "abcdefghijklmnopqrstuvwxyz")).toEqual('cb');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stringToBase', () => {
|
describe('stringToBase', () => {
|
||||||
it('should calculate the appropriate number for a string', () => {
|
it('should calculate the appropriate number for a string', () => {
|
||||||
expect(stringToBase(" ")).toEqual(BigInt(0));
|
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(1))).toEqual(BigInt(1));
|
||||||
expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(0));
|
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(2))).toEqual(BigInt(96));
|
||||||
expect(stringToBase("a")).toEqual(BigInt(65));
|
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(3))).toEqual(BigInt(9121));
|
||||||
expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(2));
|
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(4))).toEqual(BigInt(866496));
|
||||||
expect(stringToBase("ab")).toEqual(BigInt(6241));
|
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(5))).toEqual(BigInt(82317121));
|
||||||
expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(53));
|
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(6))).toEqual(BigInt(7820126496));
|
||||||
|
expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(1));
|
||||||
|
expect(stringToBase("a")).toEqual(BigInt(66));
|
||||||
|
expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(3));
|
||||||
|
expect(stringToBase("ab")).toEqual(BigInt(6337));
|
||||||
|
expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(80));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('averageBetweenStrings', () => {
|
describe('averageBetweenStrings', () => {
|
||||||
it('should average appropriately', () => {
|
it('should average appropriately', () => {
|
||||||
|
console.log(stringToBase(" "));
|
||||||
|
console.log(stringToBase("!!"));
|
||||||
|
expect(averageBetweenStrings(" ", "!!")).toEqual(" P");
|
||||||
expect(averageBetweenStrings('A', 'z')).toEqual(']');
|
expect(averageBetweenStrings('A', 'z')).toEqual(']');
|
||||||
expect(averageBetweenStrings('a', 'z', "abcdefghijklmnopqrstuvwxyz")).toEqual('m');
|
expect(averageBetweenStrings('a', 'z', "abcdefghijklmnopqrstuvwxyz")).toEqual('m');
|
||||||
expect(averageBetweenStrings('AA', 'zz')).toEqual('^.');
|
expect(averageBetweenStrings('AA', 'zz')).toEqual('^.');
|
||||||
@@ -354,22 +377,8 @@ describe("utils", function() {
|
|||||||
it('should roll over', () => {
|
it('should roll over', () => {
|
||||||
const lastAlpha = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1];
|
const lastAlpha = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1];
|
||||||
const firstAlpha = DEFAULT_ALPHABET[0];
|
const firstAlpha = DEFAULT_ALPHABET[0];
|
||||||
const secondAlpha = DEFAULT_ALPHABET[1];
|
|
||||||
|
|
||||||
// You may be looking at this and wondering how on this planet we end up with
|
const highRoll = firstAlpha + firstAlpha;
|
||||||
// the next string being +2 on the ASCII table, and this comment is here to tell
|
|
||||||
// you that you're not insane. Due to a property of the baseN conversion, our
|
|
||||||
// +1 (and -1 for prevString) turns into +2 because the first character in the
|
|
||||||
// alphabet is equivalent to zero rather than one. Thus, we're actually adding
|
|
||||||
// the second character of the alphabet (due to adding a numeric 1) to the
|
|
||||||
// input string, thus resulting in a human-understandable +2 jump rather than
|
|
||||||
// a +1 one.
|
|
||||||
|
|
||||||
// Let's validate that +1 behaviour with math
|
|
||||||
expect(stringToBase(DEFAULT_ALPHABET[0])).toEqual(BigInt(0));
|
|
||||||
expect(stringToBase(DEFAULT_ALPHABET[1])).toEqual(BigInt(1));
|
|
||||||
|
|
||||||
const highRoll = secondAlpha + firstAlpha;
|
|
||||||
const lowRoll = lastAlpha;
|
const lowRoll = lastAlpha;
|
||||||
|
|
||||||
expect(nextString(lowRoll)).toEqual(highRoll);
|
expect(nextString(lowRoll)).toEqual(highRoll);
|
||||||
@@ -386,9 +395,8 @@ describe("utils", function() {
|
|||||||
it('should properly handle rolling over at 50 characters', () => {
|
it('should properly handle rolling over at 50 characters', () => {
|
||||||
// Note: we also test reversibility of large strings here.
|
// Note: we also test reversibility of large strings here.
|
||||||
|
|
||||||
// See rollover test for why we use weird parts of the alphabet
|
|
||||||
const maxSpaceValue = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1].repeat(50);
|
const maxSpaceValue = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1].repeat(50);
|
||||||
const fiftyFirstChar = DEFAULT_ALPHABET[1] + DEFAULT_ALPHABET[0].repeat(50);
|
const fiftyFirstChar = DEFAULT_ALPHABET[0].repeat(51);
|
||||||
|
|
||||||
expect(nextString(maxSpaceValue)).toBe(fiftyFirstChar);
|
expect(nextString(maxSpaceValue)).toBe(fiftyFirstChar);
|
||||||
expect(prevString(fiftyFirstChar)).toBe(maxSpaceValue);
|
expect(prevString(fiftyFirstChar)).toBe(maxSpaceValue);
|
||||||
|
30
src/utils.ts
30
src/utils.ts
@@ -497,12 +497,29 @@ export function alphabetPad(s: string, n: number, alphabet = DEFAULT_ALPHABET):
|
|||||||
* @returns {string} The baseN number encoded as a string from the alphabet.
|
* @returns {string} The baseN number encoded as a string from the alphabet.
|
||||||
*/
|
*/
|
||||||
export function baseToString(n: bigint, alphabet = DEFAULT_ALPHABET): string {
|
export function baseToString(n: bigint, alphabet = DEFAULT_ALPHABET): string {
|
||||||
|
// Developer note: the stringToBase() function offsets the character set by 1 so that repeated
|
||||||
|
// characters (ie: "aaaaaa" in a..z) don't come out as zero. We have to reverse this here as
|
||||||
|
// otherwise we'll be wrong in our conversion. Undoing a +1 before an exponent isn't very fun
|
||||||
|
// though, so we rely on a lengthy amount of `x - 1` and integer division rules to reach a
|
||||||
|
// sane state. This also means we have to do rollover detection: see below.
|
||||||
|
|
||||||
const len = BigInt(alphabet.length);
|
const len = BigInt(alphabet.length);
|
||||||
if (n < len) {
|
if (n <= len) {
|
||||||
return alphabet[Number(n)];
|
return alphabet[Number(n) - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseToString(n / len, alphabet) + alphabet[Number(n % len)];
|
let d = n / len;
|
||||||
|
let r = Number(n % len) - 1;
|
||||||
|
|
||||||
|
// Rollover detection: if the remainder is negative, it means that the string needs
|
||||||
|
// to roll over by 1 character downwards (ie: in a..z, the previous to "aaa" would be
|
||||||
|
// "zz").
|
||||||
|
if (r < 0) {
|
||||||
|
d -= BigInt(Math.abs(r)); // abs() is just to be clear what we're doing. Could also `+= r`.
|
||||||
|
r = Number(len) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseToString(d, alphabet) + alphabet[r];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -527,9 +544,14 @@ export function stringToBase(s: string, alphabet = DEFAULT_ALPHABET): bigint {
|
|||||||
|
|
||||||
// Developer caution: we carefully cast to BigInt here to avoid losing precision. We cannot
|
// Developer caution: we carefully cast to BigInt here to avoid losing precision. We cannot
|
||||||
// rely on Math.pow() (for example) to be capable of handling our insane numbers.
|
// rely on Math.pow() (for example) to be capable of handling our insane numbers.
|
||||||
|
|
||||||
let result = BigInt(0);
|
let result = BigInt(0);
|
||||||
for (let i = s.length - 1, j = BigInt(0); i >= 0; i--, j++) {
|
for (let i = s.length - 1, j = BigInt(0); i >= 0; i--, j++) {
|
||||||
result += BigInt(s.charCodeAt(i) - alphabet.charCodeAt(0)) * (len ** j);
|
const charIndex = s.charCodeAt(i) - alphabet.charCodeAt(0);
|
||||||
|
|
||||||
|
// We add 1 to the char index to offset the whole numbering scheme. We unpack this in
|
||||||
|
// the baseToString() function.
|
||||||
|
result += BigInt(1 + charIndex) * (len ** j);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user