js-combinatorics
Simple combinatorics in JavaScript
HEADS UP: Version 2 and BigInt
Now that Internet Explorer has officially retired, It is safe to assume BigInt
is available in every JavaScript environment. From version 2.0 this module goes fully BigInt. While integer arguments can still be either number
or bigint
, all integer values that can be bigint
are always bigint
, whereas previous versions may return number
when the value <= Number.MAX_SAFE_INTEGER
. It is not only more combinatorically natural, but also makes debugging easier especially on TypeScript.
For Swift programmers
Check swift-combinatorics. More naturally implemented with generics and protocol.
SYNOPSIS
import * as $C from './combinatorics.js';
let it = new $C.Combination('abcdefgh', 4);
for (const elem of it) {
console.log(elem)
}
Usage
load everything…
import * as Combinatorics from './combinatorics.js';
or just objects you want.
import { Combination, Permutation } from './combinatorics.js';
You don't even have to install if you import
from CDNs.
import * as $C from 'https://cdn.jsdelivr.net/npm/js-combinatorics@2.1.2/combinatorics.min.js';
Since this is an ES6 module, type="module"
is required the <script>
tags. of your HTML files. But you can make it globally available as follows.
<script type="module">
import * as $C from 'combinatorics.js';
window.Combinatorics = $C;
</script>
<script>
let c = new Combinatorics.Combination('abcdefgh', 4);
</script>
node.js REPL
% node
Welcome to Node.js v16.15.0.
Type ".help" for more information.
> const $C = await import('js-combinatorics')
undefined
> $C
[Module: null prototype] {
BaseN: [class BaseN extends _CBase],
CartesianProduct: [class CartesianProduct extends _CBase],
Combination: [class Combination extends _CBase],
Permutation: [class Permutation extends _CBase],
PowerSet: [class PowerSet extends _CBase],
combinadic: [Function: combinadic],
combination: [Function: combination],
factoradic: [Function: factoradic],
factorial: [Function: factorial],
permutation: [Function: permutation],
randomInteger: [Function: randomInteger],
version: '2.1.2'
}
> [...new $C.Permutation('abcd')]
[
[ 'a', 'b', 'c', 'd' ], [ 'a', 'b', 'd', 'c' ],
[ 'a', 'c', 'b', 'd' ], [ 'a', 'c', 'd', 'b' ],
[ 'a', 'd', 'b', 'c' ], [ 'a', 'd', 'c', 'b' ],
[ 'b', 'a', 'c', 'd' ], [ 'b', 'a', 'd', 'c' ],
[ 'b', 'c', 'a', 'd' ], [ 'b', 'c', 'd', 'a' ],
[ 'b', 'd', 'a', 'c' ], [ 'b', 'd', 'c', 'a' ],
[ 'c', 'a', 'b', 'd' ], [ 'c', 'a', 'd', 'b' ],
[ 'c', 'b', 'a', 'd' ], [ 'c', 'b', 'd', 'a' ],
[ 'c', 'd', 'a', 'b' ], [ 'c', 'd', 'b', 'a' ],
[ 'd', 'a', 'b', 'c' ], [ 'd', 'a', 'c', 'b' ],
[ 'd', 'b', 'a', 'c' ], [ 'd', 'b', 'c', 'a' ],
[ 'd', 'c', 'a', 'b' ], [ 'd', 'c', 'b', 'a' ]
]
>
commonjs (node.js)
./combinatorics.js
is an ECMAScript module but if you still need a UMD or commonjs version, they are available as ./umd/combinatorics.js
and ./commonjs/combinatorics.js
respectively.
Description
Arithmetic Functions
Self-explanatory, are they not?
import { permutation, combination, factorial, randomInteger } from './combinatorics.js';
permutation(24, 12);
permutation(26, 13);
combination(56, 28);
combination(58, 29);
factorial(18);
factorial(19);
randomInteger(6402373705727999);
randomInteger(121645100408832000n);
The arithmetic functions above accept both Number
and BigInt
(if supported). Return answers always in BigInt
.
factoradic()
and combinadic()
They need a little more explanation.
import { factoradic, combinadic } from './combinatorics.js';
factoradic(6402373705727999);
factoradic(121645100408831999n);
const c16_8 = combinadic(16, 8);
c16_8(0);
c16_8(12870);
const c58_29 = combinadic(58, 29);
c58_29(0);
c58_29(30067266499541039n);
factoradic(n)
returns the factoradic representation of n
. For an array ary
with n
elements, you can get its n
th permutation by picking ary[i]
for each i
in the factoradic.
Unlike other arithmetic functions, combinadic()
returns a function which returns m
th combinadic digit of n C k
. For an array ary
with n
elements, you can get its m
th combination by picking ary[i]
for each i
in the combinadic.
classes
The module comes with Permutation
, Combination
, PowerSet
, BaseN
, and CartesianProduct
. You can individually import
them or all of them via import *
import * as $C from 'combinatorics.js';
You construct an iterable object by giving a seed iterable and options. in the example below, 'abcdefgh'
is the seed and 4
is the size of the element.
let it = new $C.Combination('abcdefgh', 4);
if you hate new
, you can use Klass.of
where Klass
is one of the classes this module offers.
let it = $C.Combination.of('abcdefgh', 4);
Once constructed, you can iterate via for … of
statement or turn it into an array via [...]
construct.
[...it];
.length
The object has .length
so you don't have to iterate to count the elements. Note the value is in bigint
so you may need to convert to number
.
it.length
[...it].length
it.length == [...it].length
it.length === [...it].length
.at()
(or .nth()
)
And the object has .at(n)
method so you can random-access each element. This is the equivalent of subscript in Array
. It was previously named .nth()
but it was renamed to .at()
ala Array.prototype.at()
in ES2020. .nth()
still available for backward compatibility.
it.at(0);
it.at(69);
at()
accepts both Number
and BigInt
.
it.at(69n);
at()
also accepts negative indexes. In which case n
is (-n)th
element from .length
.
it.at(-1);
it.at(-70);
.sample()
And .sample()
picks random element, which is defined as .at(randomInteger(.length))
.
it.sample()
Beyond Number.MAX_SAFE_INTEGER
Occasionally you need BigInt
to access elements beyond Number.MAX_SAFE_INTEGER
.
it = new $C.Permutation('abcdefghijklmnopqrstuvwxyz');
it.length;
You can still access elements before Number.MAX_SAFE_INTEGER
in Number
.
it.at(0);
it.at(9007199254740990);
But how are you goint to acccess elements beyond that? Just use BigInt
.
it.at(9007199254740991n);
it.at(it.length - 1n);
You can tell if you need BigInt
via .isBig
. Note .length
is always bigint
from version 2.0 so you may not need this method any more. So it is now deprecated.
new $C.Permutation('0123456789').isBig;
new $C.Permutation('abcdefghijklmnopqrstuvwxyz').isBig;
You can also check if it is safe on your platform via .isSafe
. It is now deprecated for the same reason as .isBig
.
new $C.Permutation('abcdefghijklmnopqrstuvwxyz').isSafe;
class Permutation
An iterable which permutes a given iterable.
new Permutation(seed, size)
seed
: the seed iterable. [...seed]
becomes the seed array.size
: the number of elements in the iterated element. defaults to seed.length
import {Permutation} from './combinatorics.js';
let it = new Permutation('abcd');
it.length;
[...it];
it = new Permutation('abcdefghijklmnopqrstuvwxyz0123456789');
it.length;
it.at(371993326789901217467999448150835199999999n);
Making a permutation of the iterable then taking its sample is functionally the same as Fisher–Yates shuffle of the iterable. Instead of shuffling the deck, it make all possible cases available and let you pick one.
it.sample();
It is in fact a little better because .sample()
only needs one random number (between 0 and .length - 1
) while Fisher–Yates needs n
random numbers.
class Combination
An iterable which emits a combination of a given iterable.
new Combination(seed, size)
seed
: the seed iterable.size
: the number of elements in the iterated element.
import {Combination} from './combinatorics.js';
let it = new Combination('abcd', 2);
it.length;
[...it];
let a100 = Array(100).fill(0).map((v,i)=>i);
it = new Combination(a100, 50);
it.length;
it.at(100891344545564193334812497255n);
class PowerSet
An iterable which emits each element of its power set.
new PowerSet(seed)
import {PowerSet} from './combinatorics.js';
let it = new PowerSet('abc');
it.length;
[...it];
it = new PowerSet(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
);
it.length;
it.at(18446744073709551615n);
class BaseN
An iterable which emits all numbers in the given system.
new BaseN(seed, size)
seed
: the seed iterable whose elements represent digits.size
: the number of digits
import {BaseN} from './combinatorics.js';
let it = new BaseN('abc', 3);
it.length;
[...it];
it = BaseN('0123456789abcdef', 16);
it.length;
it.at(18446744073709551615n);
class CartesianProduct
A cartesian product of given sets.
new CartesianProduct(...args)
args
: iterables that represent sets
import {CartesianProduct} from './combinatorics.js';
let it = new CartesianProduct('012','abc','xyz');
it.length;
[...it];
Since the number of arguments to CartesianProduct
is variable, it is sometimes helpful to give a single array with all arguments. But you cannot new ctor.apply(null, args)
this case. To mitigate that, you can use .from()
.
let a16 = Array(16).fill('0123456789abcdef');
it = CartesianProduct.from(a16);
it.length;
it.at(18446744073709551615n);
What's new from version 0.x?
js-combinatorics
has gone ES2015 since version 1.
- native iterator instead of custom
- module.
import
instead of require
. BigInt
where possible
And from version 1.2 it is written in TypeScript. combinatorics.js
and combinatorics.d.ts
are compiled from combinatorics.ts
.
APIs will change accordingly. Old versions are available in the version0
branch.
What's gone from version 0.x?
bigCombination
is gone because all classes now can handle big -- combinatorially big! -- cases thanks to BigInt support getting standard. Safari 13 and below is a major exception but BigInt is coming to Safari 14 and up.
permutationCombination
is gone because the name is misleading and it is now trivially easy to reconstruct as follow:
class permutationCombination {
constructor(seed) {
this.seed = [...seed];
}
[Symbol.iterator]() {
return function*(it){
for (let i = 1, l = it.length; i <= l; i++) {
yield* new Permutation(it, i);
}
}(this.seed);
}
}
js-combinatorics
is now natively iterable. Meaning its custom iterators are gone -- with its methods like .map
and .filter
. JS iterators are very minimalistic with only [...]
and for ... of
. But don't worry. There are several ways to make those functional methods back again.
For instance, You can use js-xiterable like so:
import {xiterable as $X} from
'https://cdn.jsdelivr.net/npm/js-xiterable@0.0.3/xiterable.min.js';
import {Permutation} from 'combinatorics.js';
let it = new Permutation('abcd');
let words = $X(it).map(v=>v.join(''))
for (const word of words)) console.log(word)