UNPKG

mphf

Version:

Hash and unhash structured model data with no collisions, duplicates or gaps using minimal perfect hash functions. Currently in alpha. Expect breaking changes for version 0. Use with caution!

162 lines (148 loc) 6.44 kB
import { Part, Choice, Tuple } from "./index.js" // 1. Define the data model for the search state const searchState = new Tuple("search", [ new Choice("category", ["electronics", "clothing", "books"]), new Choice("price", ["under_25", "25_to_50", "50_to_100", "over_100"]), new Tuple("features", [ new Choice("waterproof", ["yes", "no"]), new Choice("wireless", ["yes", "no"]), new Choice("color", ["black", "white", "red", "blue"]) ]) ]) const comprehensiveStress = new Tuple("comprehensive_test", [ // A. A large number of choices to test boundary conditions on hash size. new Choice("colors", [ "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "black", "white", "gray", "cyan", "magenta", "lime", "teal", "indigo" ]), // B. A small number of choices to test minimal hashing. new Choice("boolean_choice", ["true", "false"]), // C. Nested tuples to test deep hierarchies. new Tuple("user_settings", [ new Choice("theme", ["dark", "light", "system"]), new Choice("language", ["en", "es", "fr", "de"]), ]), // D. A tuple with a wide variety of choices inside. new Tuple("product_details", [ new Choice("size", ["xs", "s", "m", "l", "xl", "xxl"]), new Choice("material", ["cotton", "polyester", "wool", "silk"]), new Choice("stock", ["in_stock", "out_of_stock"]), new Tuple("dimensions", [ new Choice("width", ["small", "medium", "large"]), new Choice("height", ["short", "average", "tall"]) ]), ]), // E. A choice with very long, similar-looking string values to test hash collisions. new Choice("long_strings", [ "very-long-string-that-is-hard-to-distinguish-part-1", "very-long-string-that-is-hard-to-distinguish-part-2", "very-long-string-that-is-hard-to-distinguish-part-3" ]), // F. A choice with special characters that need proper encoding. new Choice("special_characters", ["!@#$", "%^&*", "()_+"]), new Tuple("parent_config", [ // This top-level Choice now contains a mix of primitive strings and nested Tuple/Choice elements. new Choice("control_type", [ // Standard primitive choice "no_control", // A nested Tuple for 'manual' control options new Tuple("manual", [ new Choice("speed", ["slow", "medium", "fast"]), new Choice("direction", ["left", "right", "straight"]) ]), // Another nested Tuple for 'automatic' control options new Tuple("automatic", [ new Choice("mode", ["eco", "sport", "comfort"]), new Choice("target", ["home", "work", "store"]) ]), // A nested Choice for 'voice' control with its own sub-options new Choice("voice", ["enabled", "disabled"]) ]) ]) ]) console.group("Random Comprehensive Stress Hash Reversal Test") for (let n = 0n; n < 250; n++) { const length = Math.random() * Part.encode(comprehensiveStress.cardinality - 1n).length const characters = [] for (let i = 0; i < length; i++) characters.push(Part.radix[Math.trunc(Math.random() * 64)]) const hash = characters.join("") const n = Part.decode(hash) if (n >= comprehensiveStress.cardinality) continue try { const hash = Part.encode(n) const nModel = comprehensiveStress.unhash(n, "bigint") const hashModel = comprehensiveStress.unhash(hash) const nReturn = comprehensiveStress.hash(nModel, "bigint") const hashReturn = comprehensiveStress.hash(hashModel) if (hash !== hashReturn) { console.log(`❌ Failed: Hash "${hash}" does not match return "${hashReturn}".`) } else if (n === nReturn) { console.log(`✅ Passed: reversed "${hash}" = ${n}.`) } if (n !== nReturn) { console.log(`❌ Failed: BigInt ${n} does not match return ${nReturn}.`) } } catch (e) { console.log(`❌ Failed: Hash "${hash}" did not reverse. Unexpected error was thrown.`, e) } } console.groupEnd() console.group("Brute Force Search State Hash Reversal Test") for (let n = 0n; n < searchState.cardinality; n++) { try { const hash = Part.encode(n) const nModel = searchState.unhash(n, "bigint") const hashModel = searchState.unhash(hash) const nReturn = searchState.hash(nModel, "bigint") const hashReturn = searchState.hash(hashModel) if (hash !== hashReturn) { if (n !== nReturn) { console.log(`❌ Failed: Input ${n} does not match return ${nReturn}.`) } else console.log(`❌ Failed: Input hash "${hash}" does not match return hash "${hashReturn}".`) } else if (n === nReturn) { console.log(`✅ Passed: reversed "${hash}" = ${n}.`) } else if (n !== nReturn) { console.log(`❌ Failed: Input ${n} does not match return ${nReturn}.`) } } catch (e) { console.log(`❌ Failed: Hash "${hash}" did not reverse. Unexpected error was thrown:`, e) } } console.groupEnd() for (const [name, target, args, TError, expectedOutput] of [ ["Out-of-Range String Test", searchState, ["hello-world-this-is-too-big-for-search-state"], RangeError], ["Out-of-Range BigInt Test", searchState, [123412341234123412341234n, "bigint"], RangeError], ["Bad Input Type Test", searchState, [16, "number"], TypeError], ["Input Type Mismatch Test - Explicit BigInt", searchState, ["17", "bigint"], TypeError], ["Input Type Mismatch Test - Explicit String", searchState, [16n, "string"], TypeError], ["Input Type Mismatch Test - Implicit Type", searchState, [16n], TypeError], ["Bad Characters Test", searchState, ["includes bad+characters$"], SyntaxError], ["Undefined Hash Test", searchState, [], TypeError], ["Single Part Hash Test - String", searchState.price.under_25, ["1"], RangeError], ["Single Part Hash Test - BigInt", searchState.price.under_25, [1n, "bigint"], RangeError], ["Single Part Hash Test - Empty String", searchState.price.under_25, [""], null, null], ["Single Part Hash Test - BigInt 0n", searchState.price.under_25, [0n, "bigint"], null, null], ["Negative BigInt Test", searchState, [-1n, "bigint"], RangeError], ["Negative BigInt Test", searchState, [-1n, "bigint"], RangeError], ]) { // Out of range test console.group(name) try { const output = target.unhash(...args) if (TError !== null) console.log("❌ Failed: Expected error was not thrown.") else if (expectedOutput !== output) console.log("❌ Failed: Unexpected output.", { output }) else console.log("✅ Passed: Expected output with no error.") } catch (e) { if (TError && e && e instanceof TError) console.log("✅ Passed: Expected error was thrown.") else console.log("❌ Failed: Unexpected error was thrown: " + e) } console.groupEnd() }