@freeword/meta
Version:
Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.
448 lines • 20.6 kB
JavaScript
import _ from 'lodash';
import { prng_alea as SeededRNGFactory } from 'esm-seedrandom';
import { throwable } from "./Outcome.js";
import * as Consts from "../UtilityConsts.js";
import { MAX_UINT32 } from "../UtilityConsts.js";
export const CHARCODE_0 = '0'.charCodeAt(0); // = 48
export const CHARCODE_9 = '9'.charCodeAt(0); // = 57
export const CHARCODE_A = 'A'.charCodeAt(0); // = 65
export const CHARCODE_Z = 'Z'.charCodeAt(0); // = 90
export const CHARCODE_a = 'a'.charCodeAt(0); // = 97
export const CHARCODE_z = 'z'.charCodeAt(0); // = 122
const CharCodeA52m1 = 52 + CHARCODE_A - 1; // 26 upper and 26 lower
const CharCodeA62m1 = 62 + CHARCODE_A - 1; // 26 upper, 26 lower, and 10 digits
// we baseline on 'A': 65-90 -> A-Z, 91-117 -> 97-122 (a-z), 117-126 -> 48-57 (0-9)
const CharOffsetA52toa = 1 + CHARCODE_Z - CHARCODE_a; // if we are at 'Z+1', we want to be at 'a': Z+1-(Z+1-a) -> a
const CharOffsetA62to0 = 27 + CHARCODE_Z - CHARCODE_0; // if we are at 'Z+27', we want to be at '0': Z+27-(Z+27-0) -> 0
const SINT32_TO_UINT32 = 2 ** 31;
export class BaseRandomFactory {
// == [Construction]
constructor(rng) {
Object.defineProperty(this, 'rng', { value: rng, configurable: true, enumerable: false });
}
static make(rngspec) {
return new this(this.rngFor(rngspec));
}
static rngFor(rngspec = Math.random) {
if (typeof rngspec === 'function') {
return rngspec;
}
if (rngspec && (typeof rngspec === 'string')) {
return this._seededRNG(rngspec);
}
throw throwable('RNG must be a non-blank string, a function, or undefined', 'badRNG', { given: rngspec });
}
static _seededRNG(seed) {
return SeededRNGFactory(seed).double;
}
// --
// == [One-shot numeric methods]
/** Random float, `0 <= x < 1` */
rand01() { return this.rng(); }
/** Random float, `lo <= x < hi` */
rand({ lo = 0, hi: hiIn = lo + 1 } = {}) {
const range = _.max([lo, hiIn]) - lo;
return lo + (this.rng() * range);
}
/** Random integer, `lo <= x <= hi` */
int(opts = {}) {
const { lo = 0, hi = lo + 9 } = opts;
return Math.floor(this.rand({ lo, hi: hi + 1 }));
}
/** Random integer, `0 <= x <= 2^32 - 1`. On some factories this is more efficient than `int({ hi: MAX_UINT32 })`. */
uint32() {
return Math.floor(this.rand01() * MAX_UINT32);
}
/** Random integer, `0 <= x <= 2^32 - 1`. On some factories this is more efficient than `int({ hi: MAX_UINT32 })`. */
sint32() {
return this.uint32() - SINT32_TO_UINT32;
}
/** Random boolean */
bool() { return (this.rand01() < 0.5); }
// --
// == [One-shot character methods]
/** Random character `loCharCode <= x < hiCharCode` */
char({ lo = CHARCODE_a, hi = CHARCODE_z } = {}) {
return String.fromCharCode(this.int({ lo, hi }));
}
/** Random character `loChar <= x < hiChar` */
charBetween({ lo: loChar = 'a', hi: hiChar = 'z' } = {}) {
const lo = loChar.charCodeAt(0);
const hi = hiChar.charCodeAt(0);
return this.char({ lo, hi });
}
/** Random lower-case letter `'a'…'z'` */
lower() { return this.charBetween({ lo: 'a', hi: 'z' }); }
/** Random upper-case letter `'A'…'Z'` */
upper() { return this.charBetween({ lo: 'A', hi: 'Z' }); }
/** Random letter `'A'…'Z'` */
azAZ() {
const charCode = this.int({ lo: CHARCODE_A, hi: CharCodeA52m1 });
if (charCode <= CHARCODE_Z) {
return String.fromCharCode(charCode);
}
return String.fromCharCode(charCode - CharOffsetA52toa);
}
/** Random character `'0'..'9'…'A'..'Z'…'a'…'z'` */
azAZ09() {
const charCode = this.int({ lo: CHARCODE_A, hi: CharCodeA62m1 });
if (charCode <= CHARCODE_Z) {
return String.fromCharCode(charCode);
}
if (charCode <= CharCodeA52m1) {
return String.fromCharCode(charCode - CharOffsetA52toa);
}
return String.fromCharCode(charCode - CharOffsetA62to0);
}
/** Random numeral `'0'…'9'` */
numeral() { return this.charBetween({ lo: '0', hi: '9' }); }
// --
// == [Streming character methods]
/** Stream of `count` random characters, `lo <= x <= hi` */
*charsStar(opts) {
for (const charCode of this.intsStar(opts)) {
yield String.fromCharCode(charCode);
}
}
/** Stream of `count` random characters, `lo <= char <= hi` */
*charsBetweenStar({ lo = 'a', hi = 'z', ...opts }) {
const loCharCode = lo.charCodeAt(0);
const hiCharCode = hi.charCodeAt(0);
for (const charCode of this.intsStar({ ...opts, lo: loCharCode, hi: hiCharCode })) {
yield String.fromCharCode(charCode);
}
}
*lowersStar(count) { yield* this.charsStar({ lo: CHARCODE_a, hi: CHARCODE_z, count }); }
*uppersStar(count) { yield* this.charsStar({ lo: CHARCODE_A, hi: CHARCODE_Z, count }); }
*numeralsStar(count) { yield* this.charsStar({ lo: CHARCODE_0, hi: CHARCODE_9, count }); }
*azAZStar(count) {
for (const charCode of this.intsStar({ lo: CHARCODE_A, hi: CharCodeA52m1, count })) {
if (charCode <= CHARCODE_Z) {
yield String.fromCharCode(charCode);
continue;
}
yield String.fromCharCode(charCode - CharOffsetA52toa);
}
}
*azAZ09Star(count) {
for (const charCode of this.intsStar({ lo: CHARCODE_A, hi: CharCodeA62m1, count })) {
if (charCode <= CHARCODE_Z) {
yield String.fromCharCode(charCode);
continue;
}
if (charCode <= CharCodeA52m1) {
yield String.fromCharCode(charCode - CharOffsetA52toa);
continue;
}
yield String.fromCharCode(charCode - CharOffsetA62to0);
}
}
// --
/** Random float, `0 <= x < 1`. @note: this makes a new factory for each call. But if you're making enough numbers to matter, you should choose one of the subclasses anduse the stream methods. */
static rand01(rngspec = Math.random) {
return this.rngFor(rngspec)();
}
/** Random float, `lo <= x < hi`. @note: this makes a new factory for each call. But if you're making enough numbers to matter, you should choose one of the subclasses anduse the stream methods. */
static rand(opts = {}, rngspec = Math.random) {
const { lo = 0, hi: hiIn = lo + 1 } = opts;
const hi = _.max([lo, hiIn]);
return lo + ((hi - lo) * this.rand01(rngspec));
}
/** Random integer, `lo <= x <= hi`. @note: this makes a new factory for each call. But if you're making enough numbers to matter, you should choose one of the subclasses anduse the stream methods. */
static int(opts = {}, rngspec = Math.random) {
const { lo = 0, hi = lo + 9 } = opts;
return Math.floor(this.rand({ ...opts, lo, hi: hi + 1 }, rngspec));
}
/** Random integer, `0 <= x <= 2^32 - 1`. Some subclasses can do this more efficiently. @note: this makes a new factory for each call. But if you're making enough numbers to matter, you should choose one of the subclasses anduse the stream methods. */
static uint32(rngspec = Math.random) { return Math.floor(this.rand01(rngspec) * MAX_UINT32); }
/** Random integer, `-2^31 <= x <= 2^31 - 1`. Some subclasses can do this more efficiently. @note: this makes a new factory for each call. But if you're making enough numbers to matter, you should choose one of the subclasses anduse the stream methods. */
static sint32(rngspec = Math.random) { return Math.floor(this.rand01(rngspec) * SINT32_TO_UINT32); }
/** Random float, `lo <= x < hi`, with only 32 bits of randomness guaranteed. Some subclasses can do this more efficiently.@note: this makes a new factory for each call. But if you're making enough numbers to matter, you should choose one of the subclasses anduse the stream methods. */
static loose01(rngspec = Math.random) { return this.rand01(rngspec); }
// --
// == [Fundamental Stream methods] -- these do the work, other stuff is sugar
/** Stream of `count` random numbers, `0 <= x < 1` */
*rand01sStar(count) {
for (let seq = 0; seq < count; seq++) {
yield this.rng();
}
}
/** Stream of `count` random numbers, `lo <= x < hi` (0 and lo + 1 by default) */
*randsStar(opts) {
const { lo = 0, hi: hiIn = lo + 1, count } = opts;
const hi = _.max([lo, hiIn]);
if (hi === 1) { // don't have to multiply if hi is 1
if (lo === 0) {
yield* this.rand01sStar(count);
return;
} // don't have to multiply or add
for (const num01 of this.rand01sStar(count)) {
yield lo + num01;
}
;
return;
}
if (lo === 0) { // don't have to add if lo is 0
for (const num01 of this.rand01sStar(count)) {
yield num01 * hi;
}
;
return;
}
const range = hi - lo;
for (const num01 of this.rand01sStar(count)) {
yield lo + (num01 * range);
}
}
/** Stream of `count` random integers, `lo <= x <= hi` -- (by default, `lo = 0` and `hi = lo + 9`) */
*intsStar(opts) {
const { lo = 0, hi = lo + 9, count } = opts;
for (const float of this.randsStar({ lo, hi: hi + 1, count })) {
yield Math.floor(float);
}
}
/** Stream of `count` random integers, `0 <= x <= 2^32 - 1`. (Some subclasses can be more efficient at this than `IntsStar`.) */
*uint32sStar(count) {
for (const float of this.rand01sStar(count)) {
yield Math.floor(float * MAX_UINT32);
}
}
/** Stream of `count` random integers, `0 <= x <= 2^32 - 1`. (Some subclasses can be more efficient at this than `IntsStar`.) */
*sint32sStar(count) {
for (const uint of this.uint32sStar(count)) {
yield uint - SINT32_TO_UINT32;
}
}
/** Stream of `count` random numbers with **32 bits of randomness**, `0 <= x < 1`. (Some subclasses can be more efficient if only 32 bits of randomness are needed.)
*/
*loose01sStar(count) {
yield* this.rand01sStar(count);
}
/** Stream of `count` random numbers with **32 bits of randomness**, `lo <= x < hi`. (Some subclasses can be more efficient if only 32 bits of randomness are needed.)
* (Some subclasses can be more efficient if only 32 bits of randomness are needed.
*/
*loosesStar(opts) {
const { lo = 0, hi = lo + 1, count } = opts;
if (lo === 0 && hi === 1) {
yield* this.loose01sStar(count);
return;
}
const range = hi - lo;
for (const num01 of this.loose01sStar(count)) {
yield lo + (num01 * range);
}
}
// --
// == [Array methods]
/** Array of `count` random numbers, `0 <= x < 1` */
rand01s(count) { return [...this.rand01sStar(count)]; }
/** Array of `count` random numbers, `lo <= x < hi` */
rands(opts) { return [...this.randsStar(opts)]; }
/** Array of `count` random integers, `lo <= x <= hi` */
randInts(opts) { return [...this.intsStar(opts)]; }
/** Array of `count` random integers, `0 <= x <= 2^32 - 1` */
randUint32s(count) { return [...this.uint32sStar(count)]; }
// --
// == [Typed Array methods]
/** Uint32Array of `count` random integers, `0 <= x < 2^32 - 1` */
randUint32Array(count) {
const array = new Uint32Array(count);
let index = 0;
for (const value of this.uint32sStar(count)) {
array[index++] = value;
}
return array;
}
/** Float32Array of `count` random numbers, `0 <= x < 1` */
randFloat32Array01(count) {
const array = new Float32Array(count);
let index = 0;
for (const value of this.loose01sStar(count)) {
array[index++] = value;
}
return array;
}
/** Float32Array of `count` random numbers, `0 <= x < 1` */
randFloat32Array(opts) {
const array = new Float32Array(opts.count);
let index = 0;
for (const value of this.loosesStar(opts)) {
array[index++] = value;
}
return array;
}
/** Float64Array of `count` random numbers with 56 bits of randomness, `0 <= x < 1`. **NOTE: that's 56 bits of randomness, not 64 bits**. */
randFloat64Array01(count) {
const array = new Float64Array(count);
let index = 0;
for (const value of this.rand01sStar(count)) {
array[index++] = value;
}
return array;
}
/** Float64Array of `count` random numbers with 56 bits of randomness, `0 <= x < 1`. **NOTE: that's 56 bits of randomness, not 64 bits**. */
randFloat64Array(opts) {
const array = new Float64Array(opts.count);
let index = 0;
for (const value of this.randsStar(opts)) {
array[index++] = value;
}
return array;
}
// --
// == [static methods for rand01s] -- stream of `count` numbers, `0 <= x < 1`]
/** Stream of `count` random numbers, `0 <= x < 1` */
static *rand01sStar(count, rngspec) {
if (!_.isNumber(count)) {
throw throwable('count must be a number', 'absentVal', count);
}
if (count < 0) {
return;
}
const factory = this.make(rngspec);
yield* factory.rand01sStar(count);
}
/** Array of `count` random numbers, `0 <= x < 1` */
static rand01s(count, rngspec) { return [...this.rand01sStar(count, rngspec)]; }
/** Float32Array of `count` random numbers, `0 <= x < 1` */
static randFloat32Array01(opts, rngspec) { const factory = this.make(rngspec); return factory.randFloat32Array01(opts.count); }
/** Float64Array of `count` random numbers with 56 bits of randomness, `0 <= x < 1`. **NOTE: that's 56 bits of randomness, not 64 bits**. */
static randFloat64Array01(opts, rngspec) { const factory = this.make(rngspec); return factory.randFloat64Array01(opts.count); }
// --
// == [static methods for rands] -- stream of `count` numbers, `lo <= x < hi`]
/** Stream of `count` random numbers, `lo <= x < hi` */
static *randsStar(opts, rngspec) { const factory = this.make(rngspec); yield* factory.randsStar(opts); }
/** Array of `count` random numbers, `lo <= x < hi` */
static rands(opts, rngspec) { const factory = this.make(rngspec); return factory.rands(opts); }
/** Float32Array of `count` random numbers, `lo <= x < hi` */
static randFloat32Array(opts, rngspec) { const factory = this.make(rngspec); return factory.randFloat32Array(opts); }
/** Float64Array of `count` random numbers with 56 bits of randomness, `lo <= x < hi`. **NOTE: that's 56 bits of randomness, not 64 bits**. */
static randFloat64Array(opts, rngspec) { const factory = this.make(rngspec); return factory.randFloat64Array(opts); }
// --
// == [static methods for randInts] -- stream of `count` integers, `lo <= x <= hi`]
/** Stream of `count` random integers, `lo <= x <= hi` */
static *intsStar(opts, rngspec) { const factory = this.make(rngspec); yield* factory.intsStar(opts); }
/** Array of `count` random integers, `lo <= x <= hi` */
static randInts(opts, rngspec) { const factory = this.make(rngspec); return factory.randInts(opts); }
// --
// == [static methods for randUint32s] -- stream of `count` integers, `0 <= x < 2^32 - 1`]
/** Stream of `count` random integers, `0 <= x <= 2^32 - 1` */
static *uint32sStar(count, rngspec) { const factory = this.make(rngspec); yield* factory.uint32sStar(count); }
/** Array of `count` random integers, `0 <= x <= 2^32 - 1` */
static randUint32s(count, rngspec) { const factory = this.make(rngspec); return factory.randUint32s(count); }
/** Uint32Array of `count` random integers, `0 <= x < 2^32 - 1` */
static randUint32Array(count, rngspec) { const factory = this.make(rngspec); return factory.randUint32Array(count); }
// --
// == [Random Selection methods] --
*RandIndicesStar(items, opts) {
const { count } = opts;
const hi = (typeof items === 'number') ? items : items.length;
for (const float of this.rand01sStar(count)) {
yield Math.floor(hi * float);
}
}
*RandChoicesStar(items, opts) {
for (const index of this.RandIndicesStar(items, opts)) {
yield items[index];
}
}
/** Naive sampling function storing the already picked values in a Set.
* Performance of this function will decrease dramatically when `k` is a
* high proportion of `n`.
*/
naiveSample(items, opts) {
const { count } = opts;
if (count >= items.length) {
return Array.from(items);
}
const seen = new Set();
const result = [];
for (const index of this.RandIndicesStar(items, { count: Infinity })) {
if (seen.has(index)) {
continue;
}
seen.add(index);
result.push(items[index]);
if (result.length >= count) {
break;
}
}
return result;
}
*TuplesStar(items, opts) {
const { count, itemLen } = opts;
const generator = this.intsStar({ lo: 0, hi: items.length, count: count * itemLen });
for (let seq = 0; seq < count; seq++) {
const tuple = [];
for (let pos = 0; pos < itemLen; pos++) {
tuple.push(items[generator.next().value]);
}
yield tuple;
}
}
/** Stream of `count` random sequences, each having `lo <= len <= hi` elements from `items`; by default, `lo = 1` and `hi = lo + 9`. Note: lo and hi are **inclusive**. */
*SequencesStar(items, opts) {
const tupleLens = this.intsStar({ ...opts, count: Infinity });
const elements = this.RandChoicesStar(items, { count: Infinity });
const { count } = opts;
for (let seq = 0; seq < count; seq++) {
const tuple = [];
const len = tupleLens.next().value;
for (let pos = 0; pos < len; pos++) {
tuple.push(elements.next().value);
}
yield tuple;
}
}
// --
// == [Accessories for randStrings] -- string built of `count` independently chosen entries from `dictionary`]
*RandStrsStar(substrings = Consts.CharsAZ09Bar, opts) {
for (const segs of this.TuplesStar(substrings, opts)) {
yield segs.join('');
}
}
*RandLenStrsStar(substrings = Consts.CharsAZ09Bar, opts) {
for (const segs of this.SequencesStar(substrings, opts)) {
yield segs.join('');
}
}
}
export class RandomFactory extends BaseRandomFactory {
}
export class SeededRandomFactory extends RandomFactory {
constructor(seed) {
const rng = SeededRNGFactory(seed);
super(rng.double);
Object.defineProperty(this, 'rng', { value: rng, configurable: true, enumerable: false });
}
static make(seed) { return new this(seed); }
static rngFor(seed) { return SeededRNGFactory(seed).double; }
/** Random float, `0 <= x < 1`. @note: this makes a new factory for each call */
static loose01(seed) { return SeededRNGFactory(seed).quick(); }
/** Random integer, `0 <= x <= 2^32 - 1`. @note: this makes a new factory for each call */
static uint32(seed) { return SeededRNGFactory(seed).int32() + SINT32_TO_UINT32; }
/** Random integer, `0 <= x <= 2^32 - 1`. @note: this makes a new factory for each call */
static sint32(seed) { return SeededRNGFactory(seed).int32(); }
*loose01sStar(count) {
for (let index = 0; index < count; index++) {
yield this.rng.quick();
}
}
*loosesStar(opts) {
const { lo = 0, hi = lo + 1, count } = opts;
if (lo === 0 && hi === 1) {
yield* this.loose01sStar(count);
return;
}
const range = hi - lo;
for (const num01 of this.loose01sStar(count)) {
yield lo + (num01 * range);
}
}
*uint32sStar(count) {
for (let index = 0; index < count; index++) {
yield this.rng.int32();
}
}
}
//# sourceMappingURL=Random.js.map