UNPKG

houser-js-utils

Version:

A comprehensive collection of TypeScript utility functions for common development tasks including array manipulation, string processing, date handling, random number generation, validation, and much more.

505 lines (504 loc) 16.1 kB
"use strict"; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); class SimpleLCG { constructor(seed) { __publicField(this, "state"); this.state = seed >>> 0; } /** * Generate next random value and update state * @returns Random number between 0 and 1 (exclusive) */ next() { this.state = 1664525 * this.state + 1013904223 >>> 0; return this.state / 4294967296; } /** * Get current state */ getState() { return this.state; } /** * Set state */ setState(state) { this.state = state >>> 0; } } class SeededRandom { /** * Creates a new seeded random generator * @param seed - Seed value or Uint8Array (up to 32 bytes) */ constructor(seed) { __publicField(this, "prng"); if (typeof seed === "number") { this.prng = new SimpleLCG(seed); } else if (seed instanceof Uint8Array) { if (seed.length > 32) { throw new RangeError("Seed must be 32 bytes or less"); } let numSeed = 0; for (let i = 0; i < Math.min(4, seed.length); i++) { numSeed = numSeed << 8 | seed[i]; } this.prng = new SimpleLCG(numSeed); } else { throw new TypeError("Seed must be a number or Uint8Array"); } } /** * Factory method that requires exact 32-byte seed * @param seed - Exactly 32 bytes of seed data * @returns New SeededRandom instance */ static fromSeed(seed) { if (!(seed instanceof Uint8Array)) { throw new TypeError("Seed must be a Uint8Array"); } if (seed.length !== 32) { throw new RangeError("Seed must be exactly 32 bytes"); } return new SeededRandom(seed); } /** * Factory method that creates generator from state * @param state - State data (simplified to number for this implementation) * @returns New SeededRandom instance */ static fromState(state) { const instance = new SeededRandom(0); instance.prng.setState(state); return instance; } /** * Factory method for simple integer seeds * @param byte - Integer between 0-255 * @returns New SeededRandom instance */ static fromFixed(byte) { if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) { throw new RangeError("Byte must be an integer between 0 and 255"); } return new SeededRandom(byte); } /** * Generate a random number between 0 and 1 (exclusive) * @returns Random number in [0,1) */ random() { return this.prng.next(); } /** * Generate a new seed for creating child PRNGs * @returns 32-byte Uint8Array suitable for seeding */ seed() { const seed = new Uint8Array(32); for (let i = 0; i < 32; i++) { seed[i] = Math.floor(this.random() * 256); } return seed; } /** * Get current generator state * @returns Current state as number */ getState() { return this.prng.getState(); } /** * Set generator state * @param state - State to set * @returns This instance for chaining */ setState(state) { this.prng.setState(state); return this; } /** * Generate random number in range * @param lo - Lower bound * @param hi - Upper bound * @param step - Optional step size * @returns Random number in specified range */ number(lo, hi, step) { return RandomUtils.number(lo, hi, step, () => this.random()); } /** * Generate random integer in range * @param lo - Lower bound (inclusive) * @param hi - Upper bound (inclusive) * @param step - Optional step size * @returns Random integer in specified range */ int(lo, hi, step) { return RandomUtils.int(lo, hi, step, () => this.random()); } /** * Generate random BigInt in range * @param lo - Lower bound (inclusive) * @param hi - Upper bound (inclusive) * @param step - Optional step size * @returns Random BigInt in specified range */ bigint(lo, hi, step) { return RandomUtils.bigint(lo, hi, step, () => this.random()); } /** * Generate random bytes * @param n - Number of bytes to generate * @returns Uint8Array of random bytes */ bytes(n) { return RandomUtils.bytes(n, () => this.random()); } /** * Fill buffer with random bytes * @param buffer - Buffer to fill * @param start - Start position (optional) * @param end - End position (optional) * @returns The filled buffer */ fillBytes(buffer, start, end) { return RandomUtils.fillBytes(buffer, start, end, () => this.random()); } } const RandomUtils = { /** * The SeededRandom class for creating reproducible random sequences */ Seeded: SeededRandom, /** * Generate a random number between 0 and 1 (exclusive) * @returns Random number in [0,1) */ random() { return Math.random(); }, /** * Generate a random number in a specified range * @param lo - Lower bound * @param hi - Upper bound * @param step - Optional step size * @param randomFn - Optional custom random function * @returns Random number in specified range */ number(lo, hi, step, randomFn = Math.random) { if (typeof lo !== "number" || typeof hi !== "number") { throw new TypeError("Lower and upper bounds must be numbers"); } if (lo >= hi) { throw new RangeError("Lower bound must be less than upper bound"); } if (step === void 0) { return lo + randomFn() * (hi - lo); } if (typeof step !== "number" || step <= 0) { throw new RangeError("Step must be a positive number"); } const maxN = Math.floor((hi - lo) / step); const N = Math.floor(randomFn() * (maxN + 1)); const result = lo + N * step; return result > hi ? hi : result; }, /** * Generate a random integer in a specified range * @param lo - Lower bound (inclusive) * @param hi - Upper bound (inclusive) * @param step - Optional step size * @param randomFn - Optional custom random function * @returns Random integer in specified range */ int(lo, hi, step, randomFn = Math.random) { if (!Number.isInteger(lo) || !Number.isInteger(hi)) { throw new TypeError("Lower and upper bounds must be integers"); } if (lo > hi) { throw new RangeError( "Lower bound must be less than or equal to upper bound" ); } if (step === void 0) { return Math.floor(randomFn() * (hi - lo + 1)) + lo; } if (!Number.isInteger(step) || step <= 0) { throw new RangeError("Step must be a positive integer"); } const maxN = Math.floor((hi - lo) / step); const N = Math.floor(randomFn() * (maxN + 1)); return lo + N * step; }, /** * Generate a random BigInt in a specified range * @param lo - Lower bound (inclusive) * @param hi - Upper bound (inclusive) * @param step - Optional step size * @param randomFn - Optional custom random function * @returns Random BigInt in specified range */ bigint(lo, hi, step, randomFn = Math.random) { if (typeof lo !== "bigint" || typeof hi !== "bigint") { throw new TypeError("Lower and upper bounds must be BigInts"); } if (lo > hi) { throw new RangeError( "Lower bound must be less than or equal to upper bound" ); } const range = hi - lo + 1n; if (step === void 0) { const randomValue = BigInt(Math.floor(randomFn() * Number(range))); return lo + randomValue; } if (typeof step !== "bigint" || step <= 0n) { throw new RangeError("Step must be a positive BigInt"); } const maxN = (hi - lo) / step; const N = BigInt(Math.floor(randomFn() * (Number(maxN) + 1))); return lo + N * step; }, /** * Generate random bytes * @param n - Number of bytes to generate * @param randomFn - Optional custom random function * @returns Uint8Array of random bytes */ bytes(n, randomFn = Math.random) { if (!Number.isInteger(n) || n < 0) { throw new RangeError("Number of bytes must be a non-negative integer"); } const bytes = new Uint8Array(n); for (let i = 0; i < n; i++) { bytes[i] = Math.floor(randomFn() * 256); } return bytes; }, /** * Fill a buffer with random bytes * @param buffer - Buffer to fill (ArrayBuffer or TypedArray) * @param start - Start position (optional) * @param end - End position (optional) * @param randomFn - Optional custom random function * @returns The filled buffer */ fillBytes(buffer, start, end, randomFn = Math.random) { let view; if (buffer instanceof ArrayBuffer) { view = new Uint8Array(buffer); } else if (ArrayBuffer.isView(buffer)) { view = new Uint8Array( buffer.buffer, buffer.byteOffset, buffer.byteLength ); } else { throw new TypeError("Buffer must be an ArrayBuffer or ArrayBufferView"); } const startPos = start ?? 0; const endPos = end ?? view.length; if (startPos < 0 || endPos > view.length || startPos > endPos) { throw new RangeError("Invalid start or end position"); } for (let i = startPos; i < endPos; i++) { view[i] = Math.floor(randomFn() * 256); } return buffer; }, /** * Generate a random boolean value * @param probability - Probability of returning true (0-1, default: 0.5) * @param randomFn - Optional custom random function * @returns Random boolean */ boolean(probability = 0.5, randomFn = Math.random) { if (typeof probability !== "number" || isNaN(probability) || probability < 0 || probability > 1) { throw new RangeError("Probability must be a number between 0 and 1"); } return randomFn() < probability; }, /** * Choose a random element from an array * @param array - Array to choose from * @param randomFn - Optional custom random function * @returns Random element from the array */ choice(array, randomFn = Math.random) { if (!Array.isArray(array)) { throw new TypeError("Input must be an array"); } if (array.length === 0) { throw new RangeError("Array cannot be empty"); } const index = Math.floor(randomFn() * array.length); return array[index]; }, /** * Choose multiple random elements from an array without replacement * @param array - Array to choose from * @param count - Number of elements to choose * @param randomFn - Optional custom random function * @returns Array of randomly chosen elements */ sample(array, count, randomFn = Math.random) { if (!Array.isArray(array)) { throw new TypeError("Input must be an array"); } if (!Number.isInteger(count) || count < 0) { throw new RangeError("Count must be a non-negative integer"); } if (count > array.length) { throw new RangeError("Count cannot be greater than array length"); } const result = []; const indices = /* @__PURE__ */ new Set(); while (result.length < count) { const index = Math.floor(randomFn() * array.length); if (!indices.has(index)) { indices.add(index); result.push(array[index]); } } return result; }, /** * Shuffle an array using Fisher-Yates algorithm * @param array - Array to shuffle * @param randomFn - Optional custom random function * @returns New shuffled array */ shuffle(array, randomFn = Math.random) { if (!Array.isArray(array)) { throw new TypeError("Input must be an array"); } const result = [...array]; for (let i = result.length - 1; i > 0; i--) { const j = Math.floor(randomFn() * (i + 1)); [result[i], result[j]] = [result[j], result[i]]; } return result; }, /** * Generate a random string of specified length * @param length - Length of the string to generate * @param charset - Character set to use (default: alphanumeric) * @param randomFn - Optional custom random function * @returns Random string */ string(length, charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", randomFn = Math.random) { if (!Number.isInteger(length) || length < 0) { throw new RangeError("Length must be a non-negative integer"); } if (typeof charset !== "string" || charset.length === 0) { throw new TypeError("Charset must be a non-empty string"); } let result = ""; for (let i = 0; i < length; i++) { result += charset[Math.floor(randomFn() * charset.length)]; } return result; }, /** * Generate a random UUID v4 * @param randomFn - Optional custom random function * @returns Random UUID string */ uuid(randomFn = Math.random) { const hex = "0123456789abcdef"; let uuid = ""; for (let i = 0; i < 36; i++) { if (i === 8 || i === 13 || i === 18 || i === 23) { uuid += "-"; } else if (i === 14) { uuid += "4"; } else if (i === 19) { uuid += hex[Math.floor(randomFn() * 4) + 8]; } else { uuid += hex[Math.floor(randomFn() * 16)]; } } return uuid; }, /** * Generate a weighted random choice * @param items - Array of items to choose from * @param weights - Array of weights corresponding to items * @param randomFn - Optional custom random function * @returns Randomly chosen item based on weights */ weightedChoice(items, weights, randomFn = Math.random) { if (!Array.isArray(items) || !Array.isArray(weights)) { throw new TypeError("Items and weights must be arrays"); } if (items.length !== weights.length) { throw new RangeError( "Items and weights arrays must have the same length" ); } if (items.length === 0) { throw new RangeError("Arrays cannot be empty"); } if (weights.some((w) => typeof w !== "number" || w < 0)) { throw new RangeError("All weights must be non-negative numbers"); } const totalWeight = weights.reduce((sum, weight) => sum + weight, 0); if (totalWeight === 0) { throw new RangeError("Total weight must be greater than 0"); } const random = randomFn() * totalWeight; let cumulativeWeight = 0; for (let i = 0; i < items.length; i++) { cumulativeWeight += weights[i]; if (random <= cumulativeWeight) { return items[i]; } } return items[items.length - 1]; }, /** * Generate a random number from normal distribution (Box-Muller transform) * @param mean - Mean of the distribution (default: 0) * @param stdDev - Standard deviation (default: 1) * @param randomFn - Optional custom random function * @returns Random number from normal distribution */ normal(mean = 0, stdDev = 1, randomFn = Math.random) { if (typeof mean !== "number" || typeof stdDev !== "number") { throw new TypeError("Mean and standard deviation must be numbers"); } if (stdDev <= 0) { throw new RangeError("Standard deviation must be positive"); } const u1 = randomFn(); const u2 = randomFn(); const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); return z0 * stdDev + mean; }, /** * Generate a random number from exponential distribution * @param rate - Rate parameter (lambda) * @param randomFn - Optional custom random function * @returns Random number from exponential distribution */ exponential(rate = 1, randomFn = Math.random) { if (typeof rate !== "number" || rate <= 0) { throw new RangeError("Rate must be a positive number"); } return -Math.log(1 - randomFn()) / rate; }, /** * Generate a random seed for seeding PRNGs * @returns 32-byte Uint8Array suitable for seeding */ seed() { return this.bytes(32); } }; exports.RandomUtils = RandomUtils; exports.SeededRandom = SeededRandom; //# sourceMappingURL=RandomUtils.js.map