UNPKG

js-combinatorics

Version:

Simple combinatorics like power set, combination, and permutation in JavaScript

413 lines (412 loc) 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CartesianProduct = exports.PowerSet = exports.BaseN = exports.Combination = exports.Permutation = exports.randomInteger = exports.combinadic = exports.factoradic = exports.factorial = exports.combination = exports.permutation = exports.version = void 0; /** * combinatorics.js * * Licensed under the MIT license. * http://www.opensource.org/licenses/mit-license.php * * @author: Dan Kogai <dankogai+github@gmail.com> * * References: * @link: http://www.ruby-doc.org/core-2.0/Array.html#method-i-combination * @link: http://www.ruby-doc.org/core-2.0/Array.html#method-i-permutation * @link: http://en.wikipedia.org/wiki/Factorial_number_system * @link: https://en.wikipedia.org/wiki/Combinatorial_number_system */ exports.version = '2.1.2'; /** * calculates `P(n, k)`. * * @link https://en.wikipedia.org/wiki/Permutation */ function permutation(n, k) { if (n < 0) throw new RangeError(`${n} is out of range`); if (k < 0) throw new RangeError(`${k} is out of range`); if (0 == k) return 1n; if (n < k) return 0n; let [bn, bk, bp] = [BigInt(n), BigInt(k), 1n]; while (bk--) bp *= bn--; return bp; } exports.permutation = permutation; /** * calculates `C(n, k)`. * * @link https://en.wikipedia.org/wiki/Combination */ function combination(n, k) { if (0 == k) return 1n; if (n == k) return 1n; if (n < k) return 0n; return permutation(n, k) / permutation(k, k); } exports.combination = combination; /** * calculates `n!` === `P(n, n)`. * * @link https://en.wikipedia.org/wiki/Factorial */ function factorial(n) { return permutation(n, n); } exports.factorial = factorial; /** * returns the factoradic representation of `n`, least significant order. * * @link https://en.wikipedia.org/wiki/Factorial_number_system * @param {number} l the number of digits */ function factoradic(n, l = 0) { if (n < 0) throw new RangeError(`${n} is out of range`); let [bn, bf] = [BigInt(n), 1n]; if (!l) { for (l = 1; bf < bn; bf *= BigInt(++l)) ; if (bn < bf) bf /= BigInt(l--); } else { bf = BigInt(factorial(l)); } let digits = [0]; for (; l; bf /= BigInt(l--)) { digits[l] = Number(bn / bf); bn %= bf; } return digits; } exports.factoradic = factoradic; /** * `combinadic(n, k)` returns a function * that takes `m` as an argument and * returns the combinadics representation of `m` for `n C k`. * * @link https://en.wikipedia.org/wiki/Combinatorial_number_system */ function combinadic(n, k) { const count = combination(n, k); const [bn, bk] = [BigInt(n), BigInt(k)]; return (m) => { if (m < 0 || count <= m) throw new RangeError(`${m} is out of range`); let digits = []; let [ba, bb] = [bn, bk]; let x = BigInt(count) - 1n - BigInt(m); for (let i = 0; i < k; i++) { ba--; while (x < combination(ba, bb)) ba--; digits.push(Number(bn - 1n - ba)); x -= combination(ba, bb); bb--; } return digits; }; } exports.combinadic = combinadic; /** * */ const _crypto = typeof crypto !== 'undefined' ? crypto : {}; const _randomBytes = typeof _crypto['randomBytes'] === 'function' ? (len) => Uint8Array.from(_crypto['randomBytes'](len)) : typeof _crypto['getRandomValues'] === 'function' ? (len) => _crypto['getRandomValues'](new Uint8Array(len)) : (len) => Uint8Array.from(Array(len), () => Math.random() * 256); /** * returns random integer `n` where `min` <= `n` < `max`: * * if the argument is `BigInt` the result is also `BigInt`. * * @param {anyint} min * @param {anyint} max */ function randomInteger(min = 0, max = Math.pow(2, 53)) { let ctor = min.constructor; if (arguments.length === 0) { return Math.floor(Math.random() * ctor(max)); } if (arguments.length == 1) { [min, max] = [ctor(0), min]; } if (typeof min == 'number') { // number [min, max] = [Math.ceil(Number(min)), Math.ceil(Number(max))]; return Math.floor(Math.random() * (max - min)) + min; } const mag = ctor(max) - ctor(min); const len = mag.toString(16).length; const u8s = _randomBytes(len); const rnd = u8s.reduce((a, v) => ((a << ctor(8)) + ctor(v)), ctor(0)); return ((ctor(rnd) * mag) >> ctor(len * 8)) + ctor(min); } exports.randomInteger = randomInteger; ; /** * Base Class of `js-combinatorics` */ class _CBase { /** * does `new` * @param args */ static of(...args) { return new (Function.prototype.bind.apply(this, [null].concat(args))); } /** * Same as `of` but takes a single array `arg` * * cf. https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible */ static from(arg) { return new (Function.prototype.bind.apply(this, [null].concat(arg))); } /** * Common iterator */ [Symbol.iterator]() { return function* (it, len) { for (let i = 0n; i < len; i++) yield it.at(i); }(this, this.length); } /** * returns `[...this]`. */ toArray() { return [...this]; } /** * @deprecated * tells wether you need `BigInt` to access all elements. */ get isBig() { return Number.MAX_SAFE_INTEGER < this.length; } /** * @deprecated * tells wether it is safe to work on this instance. * * * always `true` unless your platform does not support `BigInt`. * * if not, `true` iff `.isBig` is `false`. */ get isSafe() { return typeof BigInt !== 'undefined' || !this.isBig; } /** * check n for nth */ _check(n) { if (n < 0) { if (this.length < -n) throw new RangeError(`${n} is out of range`); return BigInt(this.length) + BigInt(n); } if (this.length <= n) throw new RangeError(`${n} is out of range`); return n; } /** * get the `n`th element of the iterator. * negative `n` goes backwards * like `Array.prototype.at()` * @link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at */ at(n) { return undefined; } /** * an alias of `at` */ nth(n) { return this.at(n); } /** * pick random element */ sample() { return this.at(randomInteger(this.length)); } /** * an infinite steam of random elements */ samples() { return function* (it) { while (true) yield it.sample(); }(this); } } /** * Permutation */ class Permutation extends _CBase { constructor(seed, size = 0) { super(); this.seed = [...seed]; this.size = 0 < size ? size : this.seed.length; this.length = permutation(this.seed.length, this.size); Object.freeze(this); } at(n) { n = this._check(n); if (n === undefined) return undefined; const offset = this.seed.length - this.size; const skip = factorial(offset); let digits = factoradic(BigInt(n) * BigInt(skip), this.seed.length); let source = this.seed.slice(); let result = []; for (let i = this.seed.length - 1; offset <= i; i--) { result.push(source.splice(digits[i], 1)[0]); } return result; } } exports.Permutation = Permutation; /** * Combination */ class Combination extends _CBase { constructor(seed, size = 0) { super(); this.seed = [...seed]; this.size = 0 < size ? size : this.seed.length; this.size = size; this.length = combination(this.seed.length, this.size); this.comb = combinadic(this.seed.length, this.size); Object.freeze(this); } /** * returns an iterator which is more efficient * than the default iterator that uses .nth * * @link https://en.wikipedia.org/wiki/Combinatorial_number_system#Applications */ bitwiseIterator() { // [Symbol.iterator]() { // console.log('overriding _CBase'); const inc = (x) => { if (x <= 0n) return 0n; const u = x & -x; const v = u + x; return v + (((v ^ x) / u) >> 2n); }; let x = (1n << BigInt(this.size)) - 1n; // 0b11...1 return function* (it, len) { for (let i = 0n; i < BigInt(len); i++, x = inc(x)) { let result = []; for (let y = x, j = 0; 0n < y; y >>= 1n, j++) { if (y & 1n) result.push(it.seed[j]); } // console.log(`x = ${x}`); yield result; } }(this, this.length); } at(n) { n = this._check(n); if (n === undefined) return undefined; let result = []; for (let i of this.comb(n)) { result.push(this.seed[i]); } return result; } } exports.Combination = Combination; /** * Base N */ class BaseN extends _CBase { constructor(seed, size = 1) { if (size < 1) throw new RangeError(`${size} is out of range`); super(); this.seed = [...seed]; this.size = size; let base = this.seed.length; this.base = base; this.length = BigInt(base) ** BigInt(size); Object.freeze(this); } at(n) { n = this._check(n); if (n === undefined) return undefined; let bn = BigInt(n); const bb = BigInt(this.base); let result = []; for (let i = 0; i < this.size; i++) { let bd = bn % bb; result.push(this.seed[Number(bd)]); bn -= bd; bn /= bb; } return result; } } exports.BaseN = BaseN; /** * Power Set */ class PowerSet extends _CBase { constructor(seed) { super(); this.seed = [...seed]; const length = 1n << BigInt(this.seed.length); this.length = length; Object.freeze(this); } at(n) { n = this._check(n); if (n === undefined) return undefined; let bn = BigInt(n); let result = []; for (let bi = 0n; bn; bn >>= 1n, bi++) if (bn & 1n) result.push(this.seed[Number(bi)]); return result; } } exports.PowerSet = PowerSet; /** * Cartesian Product */ class CartesianProduct extends _CBase { constructor(...args) { super(); this.seed = args.map(v => [...v]); this.size = this.seed.length; const length = this.seed.reduce((a, v) => a * BigInt(v.length), 1n); this.length = length; Object.freeze(this); } at(n) { n = this._check(n); if (n === undefined) return undefined; let bn = BigInt(n); let result = []; for (let i = 0; i < this.size; i++) { const base = this.seed[i].length; const bb = BigInt(base); const bd = bn % bb; result.push(this.seed[i][Number(bd)]); bn -= bd; bn /= bb; } return result; } } exports.CartesianProduct = CartesianProduct;