UNPKG

mgraph.random

Version:

Operation with seeded random numbers for mgraph.*

138 lines (124 loc) 4.45 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.mgraphRandom = factory()); })(this, (function () { 'use strict'; // index.js class Generator { constructor(seed) { this.seed = seed; } /** * Generates a random double in [0, 1) */ nextDouble() { // Robert Jenkins' 32-bit integer hash function let seed = this.seed; seed = ((seed + 0x7ed55d16) + (seed << 12)) >>> 0; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) >>> 0; seed = ((seed + 0x165667b1) + (seed << 5)) >>> 0; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) >>> 0; seed = ((seed + 0xfd7046c5) + (seed << 3)) >>> 0; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) >>> 0; this.seed = seed; return (seed & 0xfffffff) / 0x10000000; } /** * Returns a random integer in [0, maxValue) */ next(maxValue) { return Math.floor(this.nextDouble() * maxValue); } /** * Alias for nextDouble() */ uniform() { return this.nextDouble(); } /** * Returns a random number following a Gaussian distribution * (mean = 0, standard deviation = 1) */ gaussian() { let x, y, r; do { x = this.nextDouble() * 2 - 1; y = this.nextDouble() * 2 - 1; r = x * x + y * y; } while (r >= 1 || r === 0); return x * Math.sqrt(-2 * Math.log(r) / r); } /** * Returns a random number following a Lévy distribution. */ levy() { const beta = 3 / 2; const sigma = Math.pow( gamma(1 + beta) * Math.sin((Math.PI * beta) / 2) / (gamma((1 + beta) / 2) * beta * Math.pow(2, (beta - 1) / 2)), 1 / beta ); return this.gaussian() * sigma / Math.pow(Math.abs(this.gaussian()), 1 / beta); } } // Gamma function approximation. function gamma(z) { return Math.sqrt(2 * Math.PI / z) * Math.pow((1 / Math.E) * (z + 1 / (12 * z - 1 / (10 * z))), z); } /** * Creates a new seeded random number generator. * @param {number} [inputSeed] - If not provided, current time is used. */ function random(inputSeed) { const seed = typeof inputSeed === 'number' ? inputSeed : Date.now(); return new Generator(seed); } /** * Creates an iterator over an array that visits each element in random order. * The iterator provides: * - forEach(callback): Iterates over items in random order. * - shuffle(): Shuffles the array in place. * * @param {Array} array - The array to iterate over. * @param {Object} [customRandom] - An optional seeded generator that implements next(). */ function randomIterator(array, customRandom) { const localRandom = customRandom || random(); if (typeof localRandom.next !== 'function') { throw new Error("customRandom does not match expected API: next() function is missing"); } return { forEach(callback) { const arr = array; // Fisher–Yates shuffle while calling callback: for (let i = arr.length - 1; i > 0; i--) { const j = localRandom.next(i + 1); const t = arr[j]; arr[j] = arr[i]; arr[i] = t; callback(t); } if (arr.length) { callback(arr[0]); } }, shuffle() { const arr = array; for (let i = arr.length - 1; i > 0; i--) { const j = localRandom.next(i + 1); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } }; } /* * For backward compatibility, we want our default export * to be callable as a function and also expose: * random.random = random and random.randomIterator = randomIterator. */ const randomAPI = Object.assign(random, { random, randomIterator }); return randomAPI; }));