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 kB
JavaScript
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);
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);
}
};
export {
RandomUtils,
SeededRandom
};
//# sourceMappingURL=RandomUtils.mjs.map