mirror of https://github.com/vector-im/riot-web
				
				
				
			stash bigint support
							parent
							
								
									3d4411390f
								
							
						
					
					
						commit
						4af2675e23
					
				|  | @ -23,16 +23,17 @@ export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START) | |||
|     .map((_, i) => String.fromCharCode(ALPHABET_START + i)) | ||||
|     .join(""); | ||||
| 
 | ||||
| export const baseToString = (base: number, alphabet = ALPHABET): string => { | ||||
|     base = Math.floor(base); | ||||
|     if (base < alphabet.length) return alphabet[base]; | ||||
|     return baseToString(Math.floor(base / alphabet.length), alphabet) + alphabet[base % alphabet.length]; | ||||
| export const baseToString = (base: bigint, alphabet = ALPHABET): string => { | ||||
|     const len = BigInt(alphabet.length); | ||||
|     if (base < len) return alphabet[Number(base)]; | ||||
|     return baseToString(base / len, alphabet) + alphabet[Number(base % len)]; | ||||
| }; | ||||
| 
 | ||||
| export const stringToBase = (str: string, alphabet = ALPHABET): number => { | ||||
|     let result = 0; | ||||
|     for (let i = str.length - 1, j = 0; i >= 0; i--, j++) { | ||||
|         result += (str.charCodeAt(i) - alphabet.charCodeAt(0)) * (alphabet.length ** j); | ||||
| export const stringToBase = (str: string, alphabet = ALPHABET): bigint => { | ||||
|     let result = BigInt(0); | ||||
|     const len = BigInt(alphabet.length); | ||||
|     for (let i = str.length - 1, j = BigInt(0); i >= 0; i--, j++) { | ||||
|         result += BigInt(str.charCodeAt(i) - alphabet.charCodeAt(0)) * (len ** j); | ||||
|     } | ||||
|     return result; | ||||
| }; | ||||
|  | @ -51,7 +52,7 @@ export const midPointsBetweenStrings = ( | |||
|     const bPadded = pad(b, n, alphabet); | ||||
|     const aBase = stringToBase(aPadded, alphabet); | ||||
|     const bBase = stringToBase(bPadded, alphabet); | ||||
|     if (bBase - aBase - 1 < count) { | ||||
|     if (bBase - aBase - BigInt(1) < count) { | ||||
|         if (n < maxLen) { | ||||
|             // this recurses once at most due to the new limit of n+1
 | ||||
|             return midPointsBetweenStrings( | ||||
|  | @ -64,8 +65,9 @@ export const midPointsBetweenStrings = ( | |||
|         } | ||||
|         return []; | ||||
|     } | ||||
|     const step = (bBase - aBase) / (count + 1); | ||||
|     return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet)); | ||||
|     const step = (bBase - aBase) / BigInt(count + 1); | ||||
|     const start = BigInt(aBase + step); | ||||
|     return Array(count).fill(undefined).map((_, i) => baseToString(start + (BigInt(i) * step), alphabet)); | ||||
| }; | ||||
| 
 | ||||
| interface IEntry { | ||||
|  | @ -79,6 +81,7 @@ export const reorderLexicographically = ( | |||
|     toIndex: number, | ||||
|     maxLen = 50, | ||||
| ): IEntry[] => { | ||||
|     // sanity check inputs
 | ||||
|     if ( | ||||
|         fromIndex < 0 || toIndex < 0 || | ||||
|         fromIndex > orders.length || toIndex > orders.length || | ||||
|  | @ -87,41 +90,56 @@ export const reorderLexicographically = ( | |||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     // zip orders with their indices to simplify later index wrangling
 | ||||
|     const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order })); | ||||
|     // apply the fundamental order update to the zipped array
 | ||||
|     const newOrder = reorder(ordersWithIndices, fromIndex, toIndex); | ||||
| 
 | ||||
|     const isMoveTowardsRight = toIndex > fromIndex; | ||||
|     const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined; | ||||
| 
 | ||||
|     let leftBoundIdx = toIndex; | ||||
|     let rightBoundIdx = toIndex; | ||||
| 
 | ||||
|     const canDisplaceLeft = isMoveTowardsRight || orderToLeftUndefined || true; // TODO
 | ||||
|     if (canDisplaceLeft) { | ||||
|         const nextBase = newOrder[toIndex + 1]?.order !== undefined | ||||
|             ? stringToBase(newOrder[toIndex + 1].order) | ||||
|             : Number.MAX_VALUE; | ||||
|         for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) { | ||||
|             if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break; | ||||
|             leftBoundIdx = i; | ||||
|         } | ||||
|     let canMoveLeft = true; | ||||
|     const nextBase = newOrder[toIndex + 1]?.order !== undefined | ||||
|         ? stringToBase(newOrder[toIndex + 1].order) | ||||
|         : BigInt(Number.MAX_VALUE); | ||||
| 
 | ||||
|     for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) { | ||||
|         if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break; | ||||
|         leftBoundIdx = i; | ||||
|     } | ||||
| 
 | ||||
|     if (leftBoundIdx === 0 && | ||||
|         newOrder[0].order !== undefined && | ||||
|         nextBase - stringToBase(newOrder[0].order) < toIndex | ||||
|     ) { | ||||
|         canMoveLeft = false; | ||||
|     } | ||||
| 
 | ||||
|     const canDisplaceRight = !orderToLeftUndefined; | ||||
|     // TODO check if there is enough space on the right hand side at all,
 | ||||
|     // I guess find the last set order and then compare it to prevBase + $requiredGap
 | ||||
|     let canMoveRight = canDisplaceRight; | ||||
|     if (canDisplaceRight) { | ||||
|         const prevBase = newOrder[toIndex - 1]?.order !== undefined | ||||
|             ? stringToBase(newOrder[toIndex - 1]?.order) | ||||
|             : Number.MIN_VALUE; | ||||
|             : BigInt(Number.MIN_VALUE); | ||||
| 
 | ||||
|         for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) { | ||||
|             if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
 | ||||
|             rightBoundIdx = i; | ||||
|         } | ||||
| 
 | ||||
|         if (rightBoundIdx === newOrder.length - 1 && | ||||
|             (newOrder[rightBoundIdx] | ||||
|                 ? stringToBase(newOrder[rightBoundIdx].order) | ||||
|                 : BigInt(Number.MAX_VALUE)) - prevBase <= (rightBoundIdx - toIndex) | ||||
|         ) { | ||||
|             canMoveRight = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const leftDiff = toIndex - leftBoundIdx; | ||||
|     const rightDiff = rightBoundIdx - toIndex; | ||||
|     const leftDiff = canMoveLeft ? toIndex - leftBoundIdx : Number.MAX_SAFE_INTEGER; | ||||
|     const rightDiff = canMoveRight ? rightBoundIdx - toIndex : Number.MAX_SAFE_INTEGER; | ||||
| 
 | ||||
|     if (orderToLeftUndefined || leftDiff < rightDiff) { | ||||
|         rightBoundIdx = toIndex; | ||||
|  | @ -130,14 +148,13 @@ export const reorderLexicographically = ( | |||
|     } | ||||
| 
 | ||||
|     const prevOrder = newOrder[leftBoundIdx - 1]?.order | ||||
|         ?? String.fromCharCode(ALPHABET_START).repeat(5); // TODO
 | ||||
|         ?? String.fromCharCode(ALPHABET_START).repeat(5); | ||||
|     const nextOrder = newOrder[rightBoundIdx + 1]?.order | ||||
|         ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1); // TODO
 | ||||
|         ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length); | ||||
| 
 | ||||
|     const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen); | ||||
|     // TODO If we exceed maxLen then reorder EVERYTHING
 | ||||
| 
 | ||||
|     console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff }); | ||||
|     console.log("@@ test", { canMoveLeft, canMoveRight, prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff }); | ||||
| 
 | ||||
|     return changes.map((order, i) => { | ||||
|         const index = newOrder[leftBoundIdx + i].index; | ||||
|  |  | |||
|  | @ -46,28 +46,28 @@ const moveLexicographicallyTest = ( | |||
| 
 | ||||
| describe("stringOrderField", () => { | ||||
|     it("stringToBase", () => { | ||||
|         expect(stringToBase(" ")).toBe(0); | ||||
|         expect(stringToBase("a")).toBe(65); | ||||
|         expect(stringToBase("aa")).toBe(6240); | ||||
|         expect(stringToBase("cat")).toBe(610934); | ||||
|         expect(stringToBase("doggo")).toBe(5607022724); | ||||
|         expect(stringToBase(" ")).toEqual(0); | ||||
|         expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(0); | ||||
|         expect(stringToBase("a")).toEqual(65); | ||||
|         expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(2); | ||||
|         expect(stringToBase("ab")).toEqual(6241); | ||||
|         expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(53); | ||||
|         expect(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual(4.5115969857961825e+78); | ||||
|         expect(stringToBase("~".repeat(50))).toEqual(7.694497527671333e+98); | ||||
|         expect(Number(stringToBase(" "))).toBe(0); | ||||
|         expect(Number(stringToBase("a"))).toBe(65); | ||||
|         expect(Number(stringToBase("aa"))).toBe(6240); | ||||
|         expect(Number(stringToBase("cat"))).toBe(610934); | ||||
|         expect(Number(stringToBase("doggo"))).toBe(5607022724); | ||||
|         expect(Number(stringToBase(" "))).toEqual(0); | ||||
|         expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(0); | ||||
|         expect(Number(stringToBase("a"))).toEqual(65); | ||||
|         expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(2); | ||||
|         expect(Number(stringToBase("ab"))).toEqual(6241); | ||||
|         expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(53); | ||||
|         expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.511596985796182e+78); | ||||
|         expect(Number(stringToBase("~".repeat(50)))).toEqual(7.694497527671333e+98); | ||||
|         // expect(typeof stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual("bigint");
 | ||||
|     }); | ||||
| 
 | ||||
|     it("baseToString", () => { | ||||
|         expect(baseToString(10)).toBe(ALPHABET[10]); | ||||
|         expect(baseToString(10, "abcdefghijklmnopqrstuvwxyz")).toEqual("k"); | ||||
|         expect(baseToString(6241)).toEqual("ab"); | ||||
|         expect(baseToString(53, "abcdefghijklmnopqrstuvwxyz")).toEqual("cb"); | ||||
|         expect(baseToString(1234)).toBe(",~"); | ||||
|         expect(baseToString(BigInt(10))).toBe(ALPHABET[10]); | ||||
|         expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("k"); | ||||
|         expect(baseToString(BigInt(6241))).toEqual("ab"); | ||||
|         expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("cb"); | ||||
|         expect(baseToString(BigInt(1234))).toBe(",~"); | ||||
|     }); | ||||
| 
 | ||||
|     it("midPointsBetweenStrings", () => { | ||||
|  | @ -122,7 +122,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test A moving to the start when all is undefined", () => { | ||||
|     it("test moving to the start when all is undefined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [undefined, undefined, undefined, undefined], | ||||
|             2, | ||||
|  | @ -131,7 +131,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test B moving to the end when all is undefined", () => { | ||||
|     it("test moving to the end when all is undefined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [undefined, undefined, undefined, undefined], | ||||
|             1, | ||||
|  | @ -140,7 +140,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test C moving left when all is undefined", () => { | ||||
|     it("test moving left when all is undefined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [undefined, undefined, undefined, undefined, undefined, undefined], | ||||
|             4, | ||||
|  | @ -149,7 +149,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test D moving right when all is undefined", () => { | ||||
|     it("test moving right when all is undefined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [undefined, undefined, undefined, undefined], | ||||
|             1, | ||||
|  | @ -158,7 +158,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test E moving more right when all is undefined", () => { | ||||
|     it("test moving more right when all is undefined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined], | ||||
|             1, | ||||
|  | @ -167,7 +167,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test F moving left when right is undefined", () => { | ||||
|     it("test moving left when right is undefined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["20", undefined, undefined, undefined, undefined, undefined], | ||||
|             4, | ||||
|  | @ -176,7 +176,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test G moving right when right is undefined", () => { | ||||
|     it("test moving right when right is undefined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined], | ||||
|             1, | ||||
|  | @ -185,7 +185,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test H moving left when right is defined", () => { | ||||
|     it("test moving left when right is defined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["10", "20", "30", "40", undefined, undefined], | ||||
|             3, | ||||
|  | @ -194,7 +194,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test I moving right when right is defined", () => { | ||||
|     it("test moving right when right is defined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["10", "20", "30", "40", "50", undefined], | ||||
|             1, | ||||
|  | @ -203,7 +203,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test J moving left when all is defined", () => { | ||||
|     it("test moving left when all is defined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["11", "13", "15", "17", "19"], | ||||
|             2, | ||||
|  | @ -212,7 +212,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test K moving right when all is defined", () => { | ||||
|     it("test moving right when all is defined", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["11", "13", "15", "17", "19"], | ||||
|             1, | ||||
|  | @ -221,7 +221,7 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test L moving left into no left space", () => { | ||||
|     it.skip("test moving left into no left space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["11", "12", "13", "14", "19"], | ||||
|             3, | ||||
|  | @ -231,17 +231,17 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test M moving right into no right space", () => { | ||||
|     it("test moving right into no right space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["15", "16", "17", "18", "19"], | ||||
|             1, | ||||
|             3, | ||||
|             2, | ||||
|             3, | ||||
|             2, | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test N moving right into no left space", () => { | ||||
|     it("test moving right into no left space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["11", "12", "13", "14", "15", "16", undefined], | ||||
|             1, | ||||
|  | @ -250,13 +250,92 @@ describe("stringOrderField", () => { | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test O moving left into no right space", () => { | ||||
|     it("test moving left into no right space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["15", "16", "17", "18", "19"], | ||||
|             4, | ||||
|             3, | ||||
|             4, | ||||
|             2, | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test moving left into no left space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [ | ||||
|                 ALPHABET.charAt(0), | ||||
|                 ALPHABET.charAt(1), | ||||
|                 ALPHABET.charAt(2), | ||||
|                 ALPHABET.charAt(3), | ||||
|                 ALPHABET.charAt(4), | ||||
|                 ALPHABET.charAt(5), | ||||
|             ], | ||||
|             5, | ||||
|             1, | ||||
|             5, | ||||
|             1, | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test moving right into no right space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [ | ||||
|                 ALPHABET.charAt(ALPHABET.length - 5), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 4), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 3), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 2), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 1), | ||||
|             ], | ||||
|             1, | ||||
|             3, | ||||
|             3, | ||||
|             1, | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test moving right into no left space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             ["0", "1", "2", "3", "4", "5"], | ||||
|             1, | ||||
|             3, | ||||
|             3, | ||||
|             1, | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it("test moving left into no right space", () => { | ||||
|         moveLexicographicallyTest( | ||||
|             [ | ||||
|                 ALPHABET.charAt(ALPHABET.length - 5), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 4), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 3), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 2), | ||||
|                 ALPHABET.charAt(ALPHABET.length - 1), | ||||
|             ], | ||||
|             4, | ||||
|             3, | ||||
|             4, | ||||
|             1, | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     const prev = (str: string) => baseToString(stringToBase(str) - BigInt(1)); | ||||
|     const next = (str: string) => baseToString(stringToBase(str) + BigInt(1)); | ||||
| 
 | ||||
|     it("baseN calculation is correctly consecutive", () => { | ||||
|         const str = "this-is-a-test"; | ||||
|         expect(next(prev(str))).toBe(str); | ||||
|     }); | ||||
| 
 | ||||
|     it("rolls over sanely", () => { | ||||
|         const maxSpaceValue = "~".repeat(50); | ||||
|         const fiftyFirstChar = "!" + " ".repeat(50); | ||||
|         expect(next(maxSpaceValue)).toBe(fiftyFirstChar); | ||||
|         expect(prev(fiftyFirstChar)).toBe(maxSpaceValue); | ||||
|         expect(stringToBase(ALPHABET[0])).toEqual(BigInt(0)); | ||||
|         expect(stringToBase(ALPHABET[1])).toEqual(BigInt(1)); | ||||
|         expect(ALPHABET[ALPHABET.length - 1]).toBe("~"); | ||||
|         expect(ALPHABET[0]).toBe(" "); | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Michael Telatynski
						Michael Telatynski