Code Monkey home page Code Monkey logo

js-combinatorics's Introduction

ES2020 MIT LiCENSE CI via GitHub Actions

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) // ['a', 'b', 'c', 'd'] ... ['e', 'f', 'g', 'h']
}

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/[email protected]/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>
  // now you can access Combinatorics
  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);  // 1295295050649600n
permutation(26, 13);  // 64764752532480000n

combination(56, 28);  // 7648690600760440n
combination(58, 29);  // 30067266499541040n

factorial(18);  // 6402373705728000n
factorial(19);  // 121645100408832000n

randomInteger(6402373705727999);    // random n  [0,6402373705728000)
randomInteger(121645100408832000n); // ramdom n  [0n, 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);     // [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]
factoradic(121645100408831999n);  // [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]

const c16_8 = combinadic(16, 8);
c16_8(0);     // [ 0,  1,  2,  3,  4,  5,  6,  7]
c16_8(12870); // [ 8,  9, 10, 11, 12, 13, 14, 15]
const c58_29 = combinadic(58, 29);
c58_29(0); /* [
   0,  1,  2,  3,  4,  5,  6,  7,  8,
   9, 10, 11, 12, 13, 14, 15, 16, 17,
  18, 19, 20, 21, 22, 23, 24, 25, 26,
  27, 28
] */
c58_29(30067266499541039n); /* [
  29, 30, 31, 32, 33, 34, 35, 36, 37,
  38, 39, 40, 41, 42, 43, 44, 45, 46,
  47, 48, 49, 50, 51, 52, 53, 54, 55,
  56, 57
] */

factoradic(n) returns the factoradic representation of n. For an array ary with n elements, you can get its nth permutation by picking ary[i] for each i in the factoradic.

Unlike other arithmetic functions, combinadic() returns a function which returns mth combinadic digit of n C k. For an array ary with n elements, you can get its mth 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]; /* [
  [ 'a', 'b', 'c', 'd' ], [ 'a', 'b', 'c', 'e' ], [ 'a', 'b', 'c', 'f' ],
  [ 'a', 'b', 'c', 'g' ], [ 'a', 'b', 'c', 'h' ], [ 'a', 'b', 'd', 'e' ],
  [ 'a', 'b', 'd', 'f' ], [ 'a', 'b', 'd', 'g' ], [ 'a', 'b', 'd', 'h' ],
  [ 'a', 'b', 'e', 'f' ], [ 'a', 'b', 'e', 'g' ], [ 'a', 'b', 'e', 'h' ],
  [ 'a', 'b', 'f', 'g' ], [ 'a', 'b', 'f', 'h' ], [ 'a', 'b', 'g', 'h' ],
  [ 'a', 'c', 'd', 'e' ], [ 'a', 'c', 'd', 'f' ], [ 'a', 'c', 'd', 'g' ],
  [ 'a', 'c', 'd', 'h' ], [ 'a', 'c', 'e', 'f' ], [ 'a', 'c', 'e', 'g' ],
  [ 'a', 'c', 'e', 'h' ], [ 'a', 'c', 'f', 'g' ], [ 'a', 'c', 'f', 'h' ],
  [ 'a', 'c', 'g', 'h' ], [ 'a', 'd', 'e', 'f' ], [ 'a', 'd', 'e', 'g' ],
  [ 'a', 'd', 'e', 'h' ], [ 'a', 'd', 'f', 'g' ], [ 'a', 'd', 'f', 'h' ],
  [ 'a', 'd', 'g', 'h' ], [ 'a', 'e', 'f', 'g' ], [ 'a', 'e', 'f', 'h' ],
  [ 'a', 'e', 'g', 'h' ], [ 'a', 'f', 'g', 'h' ], [ 'b', 'c', 'd', 'e' ],
  [ 'b', 'c', 'd', 'f' ], [ 'b', 'c', 'd', 'g' ], [ 'b', 'c', 'd', 'h' ],
  [ 'b', 'c', 'e', 'f' ], [ 'b', 'c', 'e', 'g' ], [ 'b', 'c', 'e', 'h' ],
  [ 'b', 'c', 'f', 'g' ], [ 'b', 'c', 'f', 'h' ], [ 'b', 'c', 'g', 'h' ],
  [ 'b', 'd', 'e', 'f' ], [ 'b', 'd', 'e', 'g' ], [ 'b', 'd', 'e', 'h' ],
  [ 'b', 'd', 'f', 'g' ], [ 'b', 'd', 'f', 'h' ], [ 'b', 'd', 'g', 'h' ],
  [ 'b', 'e', 'f', 'g' ], [ 'b', 'e', 'f', 'h' ], [ 'b', 'e', 'g', 'h' ],
  [ 'b', 'f', 'g', 'h' ], [ 'c', 'd', 'e', 'f' ], [ 'c', 'd', 'e', 'g' ],
  [ 'c', 'd', 'e', 'h' ], [ 'c', 'd', 'f', 'g' ], [ 'c', 'd', 'f', 'h' ],
  [ 'c', 'd', 'g', 'h' ], [ 'c', 'e', 'f', 'g' ], [ 'c', 'e', 'f', 'h' ],
  [ 'c', 'e', 'g', 'h' ], [ 'c', 'f', 'g', 'h' ], [ 'd', 'e', 'f', 'g' ],
  [ 'd', 'e', 'f', 'h' ], [ 'd', 'e', 'g', 'h' ], [ 'd', 'f', 'g', 'h' ],
  [ 'e', 'f', 'g', 'h' ]
] */

.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       // 70n
[...it].length  // 70
it.length ==  [...it].length // true because comparisons work between number and bigint
it.length === [...it].length // false because types are different

.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);  //  [ 'a', 'b', 'c', 'd' ];
it.at(69); //  [ 'a', 'd', 'c', 'h' ];

at() accepts both Number and BigInt.

it.at(69n);  // [ 'a', 'd', 'c', 'h' ];

at() also accepts negative indexes. In which case n is (-n)th element from .length.

it.at(-1);   // [ 'a', 'd', 'c', 'h' ]
it.at(-70);  // [ 'a', 'b', 'c', 'd' ]

.sample()

And .sample() picks random element, which is defined as .at(randomInteger(.length)).

it.sample() // one of ['a', 'b', 'c', 'd'] ... ['a', 'd', 'e', 'f']

Beyond Number.MAX_SAFE_INTEGER

Occasionally you need BigInt to access elements beyond Number.MAX_SAFE_INTEGER.

it = new $C.Permutation('abcdefghijklmnopqrstuvwxyz');
it.length;  // 403291461126605635584000000n

You can still access elements before Number.MAX_SAFE_INTEGER in Number.

it.at(0);  /* [
  'a', 'b', 'c', 'd', 'e', 'f',
  'g', 'h', 'i', 'j', 'k', 'l',
  'm', 'n', 'o', 'p', 'q', 'r',
  's', 't', 'u', 'v', 'w', 'x',
  'y', 'z'
] */
it.at(9007199254740990); /* [
  'a', 'b', 'c', 'd', 'e', 'f',
  'g', 'i', 'p', 'n', 'r', 'z',
  'm', 'h', 'y', 'x', 'u', 't',
  'l', 'j', 'k', 'q', 's', 'o',
  'v', 'w'
] */

But how are you goint to acccess elements beyond that? Just use BigInt.

it.at(9007199254740991n);  /* [
  'a', 'b', 'c', 'd', 'e', 'f',
  'g', 'i', 'p', 'n', 'r', 'z',
  'm', 'h', 'y', 'x', 'u', 't',
  'l', 'j', 'k', 'q', 's', 'o',
  'w', 'v'
] */
it.at(it.length - 1n); /* [
  'z', 'y', 'x', 'w', 'v', 'u',
  't', 's', 'r', 'q', 'p', 'o',
  'n', 'm', 'l', 'k', 'j', 'i',
  'h', 'g', 'f', 'e', 'd', 'c',
  'b', 'a'
] */

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; // false
new $C.Permutation('abcdefghijklmnopqrstuvwxyz').isBig; // true

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; // always true

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'); // size 4 is assumed
it.length;  // 24n
[...it];    /* [
  [ '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' ]
] */

it = new Permutation('abcdefghijklmnopqrstuvwxyz0123456789');
it.length;  // 371993326789901217467999448150835200000000n
it.at(371993326789901217467999448150835199999999n);  /* [
  '9', '8', '7', '6', '5', '4', '3',
  '2', '1', '0', 'z', 'y', 'x', 'w',
  'v', 'u', 't', 's', 'r', 'q', 'p',
  'o', 'n', 'm', 'l', 'k', 'j', 'i',
  'h', 'g', 'f', 'e', 'd', 'c', 'b',
  'a'
] */

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(); // something between ['a','b', ... '9'] and ['9','8',....'a'] 

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;  // 6n
[...it];    /* [
  [ 'a', 'b' ],
  [ 'a', 'c' ],
  [ 'a', 'd' ],
  [ 'b', 'c' ],
  [ 'b', 'd' ],
  [ 'c', 'd' ]
] */

let a100 = Array(100).fill(0).map((v,i)=>i); // [0, 1, ...99]
it = new Combination(a100, 50);
it.length;  // 100891344545564193334812497256n
it.at(100891344545564193334812497255n);  /* [
  50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
  61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
  72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
  83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
  94, 95, 96, 97, 98, 99
] */

class PowerSet

An iterable which emits each element of its power set.

new PowerSet(seed)

  • seed: the seed iterable.
import {PowerSet} from './combinatorics.js';

let it = new PowerSet('abc');
it.length;  // 8n
[...it];    /* [
  [],
  [ 'a' ],
  [ 'b' ],
  [ 'a', 'b' ],
  [ 'c' ],
  [ 'a', 'c' ],
  [ 'b', 'c' ],
  [ 'a', 'b', 'c' ]
] */

it = new PowerSet(
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
);
it.length;  // 18446744073709551616n
it.at(18446744073709551615n);  /* [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
  'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
  'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a',
  'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
  'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
  't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
  '2', '3', '4', '5', '6', '7', '8', '9', '+',
  '/'
] */

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;  // 27n
[...it];    /* [
  [ 'a', 'a', 'a' ], [ 'b', 'a', 'a' ],
  [ 'c', 'a', 'a' ], [ 'a', 'b', 'a' ],
  [ 'b', 'b', 'a' ], [ 'c', 'b', 'a' ],
  [ 'a', 'c', 'a' ], [ 'b', 'c', 'a' ],
  [ 'c', 'c', 'a' ], [ 'a', 'a', 'b' ],
  [ 'b', 'a', 'b' ], [ 'c', 'a', 'b' ],
  [ 'a', 'b', 'b' ], [ 'b', 'b', 'b' ],
  [ 'c', 'b', 'b' ], [ 'a', 'c', 'b' ],
  [ 'b', 'c', 'b' ], [ 'c', 'c', 'b' ],
  [ 'a', 'a', 'c' ], [ 'b', 'a', 'c' ],
  [ 'c', 'a', 'c' ], [ 'a', 'b', 'c' ],
  [ 'b', 'b', 'c' ], [ 'c', 'b', 'c' ],
  [ 'a', 'c', 'c' ], [ 'b', 'c', 'c' ],
  [ 'c', 'c', 'c' ]
] */

it = BaseN('0123456789abcdef', 16);
it.length;  // 18446744073709551616n
it.at(18446744073709551615n);  /* [
  'f', 'f', 'f', 'f',
  'f', 'f', 'f', 'f',
  'f', 'f', 'f', 'f',
  'f', 'f', 'f', 'f'
] */

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;  // 27n
[...it];    /* [
  [ '0', 'a', 'x' ], [ '1', 'a', 'x' ],
  [ '2', 'a', 'x' ], [ '0', 'b', 'x' ],
  [ '1', 'b', 'x' ], [ '2', 'b', 'x' ],
  [ '0', 'c', 'x' ], [ '1', 'c', 'x' ],
  [ '2', 'c', 'x' ], [ '0', 'a', 'y' ],
  [ '1', 'a', 'y' ], [ '2', 'a', 'y' ],
  [ '0', 'b', 'y' ], [ '1', 'b', 'y' ],
  [ '2', 'b', 'y' ], [ '0', 'c', 'y' ],
  [ '1', 'c', 'y' ], [ '2', 'c', 'y' ],
  [ '0', 'a', 'z' ], [ '1', 'a', 'z' ],
  [ '2', 'a', 'z' ], [ '0', 'b', 'z' ],
  [ '1', 'b', 'z' ], [ '2', 'b', 'z' ],
  [ '0', 'c', 'z' ], [ '1', 'c', 'z' ],
  [ '2', 'c', 'z' ]
] */

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;  // 18446744073709551616n
it.at(18446744073709551615n);  /* [
  'f', 'f', 'f', 'f',
  'f', 'f', 'f', 'f',
  'f', 'f', 'f', 'f',
  'f', 'f', 'f', 'f'
] */

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/[email protected]/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)
/*
abcd
abdc
...
dcab
dcba
*/

js-combinatorics's People

Contributors

atavakoli avatar battila7 avatar blanchg avatar dankogai avatar dbkaplun avatar iamstolis avatar lukasdrgon avatar markus-willems avatar michsior14 avatar molarmanful avatar mypipsen avatar papandreou avatar quanyails avatar ramgole avatar sophietk avatar ssartell avatar tajnymag avatar tansandoteth avatar vzvu3k6k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

js-combinatorics's Issues

Maybe allow array of arrays as input for cartesianProduct

I currently have to apply some hackery to be able to input an array of arrays:

Combinatorics.cartesianProduct().apply(this, [[1,2,3],[4,5,6],[7,8,9]])

It would be nice to also allow for this kind of input:

Combinatorics.cartesianProduct([[1,2,3],[4,5,6],[7,8,9]])

0.4.1 introduces breaking change over 0.4.0

My project depends on ^0.4.0, and as soon as we picked up 0.4.1 we got a breaking change because of the switch from exporting the Combinatorics object to the global namespace (compare 0.4.0 and 0.4.1). Example:

# snippet of "index.js"
7: var combinatorics = require('js-combinatorics').Combinatorics;
...
72: var possibleFilenames = _(combinatorics.power(flags).toArray())

# results in
TypeError: Cannot read property 'power' of undefined
    at coalesceFiles (/Users/daniel.ramagem/.../index.js:72:44)

As it stands now the package should have the major version reved to 1 (e.g., 1.0.0) since this was a breaking API change (see semver reference).

UPDATE: FWIW a colleague just pointed out that before "1.0.0" a project is considered unstable so you can't necessarily expect adherence to the semver ordering.

Can cartesianProduct accept List of List?

Example from README.md:

cp = Combinatorics.cartesianProduct([0, 1, 2], [0, 10, 20], [0, 100, 200]);

Is there a way to do like this?

var x = [[0, 1, 2], [0, 10, 20], [0, 100, 200]]
cp = Combinatorics.cartesianProduct(x);

Interactive documentation

It would be great to write an html page with interactive documentation of js-combinatorics.

This is really simple to do that with the klipse plugin.

Disclaimer: I'm the author of KLIPSE

Specify the maximum items count for `combination`

Unless I am mistaken, if we have more than 31 items we should use bigCombination instead of combination? It might be helpful for people to clarify this in the README, for example.

Thanks for this great work! :)

Source file is missing in repo

Hi,

It would be helpful for us if you also commit source file. Right now, there is only minified file present in repo.

Thanks,
Pavan

Does not work in angular.js environment

I followed the install directions:

  1. install with bower
  2. add script include in the html file.

I also added the 'Combinatorics' object into the core.module file

I get this error:

Module 'Combinatorics' is not available!

This does not seem to work as is in angular.js environment

Use native generators instead of custom iteration methods

Hi, first of all, thanks a lot for building this awesome lib.
I was almost going to implement my own, but I found this and it suits my needs almost perfectly.

The implementation of most functions in this lib is lazy and that's really a good design choice because combinatorics are things just bound to explode in size. However, being forced to consume them in while loops feels a bit outdated.

const cmb = combination([1, 2, 3], 2);
let item;
while((item = cmb.next()) {
  console.log(item);
}

You are probably aware, but JavaScript Generators gives us a more native way of expressing lazy behavior.

I wrote some tiny wrappers around the functions this lib exposes, such as:

import { combination as originalCombination } from 'js-combinatorics';

export default function combination(arr, size) {
  return {
    *[Symbol.iterator]() {
      const cmb = originalCombination(arr, size);

      let item;
      while ((item = cmb.next())) {
        yield item;
      }
    },
  };
}

This way, I can consume it with a for..of loop:

const cmb = combination([1, 2, 3], 2);
for (let item of cmb) {
  console.log(i);
}

Or could even use the spread operator to turn it into an array:

const cmb = combination([1, 2, 3], 2);
const arr = [...cmb];
console.log(arr);

Maybe this could be made built-in. If you are willing to give this a try, I could work on a PR as soon as I have some time off.

Is there a "hasNext" predicate?

According to the documentation, the generators have a "next" function that returns "undefined" if there are no more elements. But, there is no way to check whether there are more elements, without consuming the next element.

Is it possible to add a boolean "hasNext" function (similar to the function with that name in Java) that returns "true" if there are more elements, but does not remove an element from the list?

Allow serializing the iterators

I'd like to save the state of the iterator to a file, so that I can continue iterating at that exact point on next run. A few values are held via closure, so I can't just serialize the iterator itself. Any ideas?

Fetch results on demand like pagination for large output

Thank you for this great library, very useful.

I am wondering, if is it is possible to create pagination for large output and create and fetch only required output based on page number to make it memory efficient.

It is easy to create pagination when you have all results in hand, but it is not memory efficient.

Is there a way to do that ?

v1.2.x does not work with native Node.js ESM

If I run the following code snippet:

import { Combination } from "js-combinatorics";

const allPossible = new Combination(["en", "es", "pt", "fr", "tr"], 2);

console.log([...allPossible]);

I get the following error:

import { Combination } from 'js-combinatorics';
         ^^^^^^^^^^^
SyntaxError: The requested module 'js-combinatorics' is expected to be of type CommonJS, which does not support named exports. CommonJS modules can be imported by importing the default export.
For example:
import pkg from 'js-combinatorics';
const { Combination } = pkg;
    at ModuleJob._instantiate (internal/modules/esm/module_job.js:98:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:137:5)
    at async Loader.import (internal/modules/esm/loader.js:162:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)

Looks like the package.json of version 1.2.1 is missing the required "type": "module" config in order to work properly with Node.js >=13.

Currenlty I get the following package.json after installing:

{
  "name": "js-combinatorics",
  "version": "1.2.1",
  "description": "Simple combinatorics like power set, combination, and permutation in JavaScript",
  "main": "combinatorics.js",
  "module": "combinatorics.js",
  "types": "combinatorics.d.ts",
  "files": [
    "combinatorics.d.ts",
    "combinatorics.js"
  ],
  "runkitExample": "require=require(\"esm\")(module);\nvar Combinatorics=require(\"js-combinatorics\");\n",
  "devDependencies": {
    "typescript": "^3.9.7",
    "@types/node": "^14.0.26",
    "mocha": "^8.0.0",
    "chai": "^4.2.0"
  },
  "scripts": {
    "test": "make test"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/dankogai/js-combinatorics.git"
  },
  "keywords": [
    "Combinatorics",
    "combination",
    "permutation"
  ],
  "author": "Dan Kogai",
  "license": "MIT",
  "readmeFilename": "README.md",
  "gitHead": "e3684eb54f7c6fbb24bb7a4d08d88671ce12e9eb"
}

If I change the package.json for js-combinatorics inside node_modules like this:

{
  "name": "js-combinatorics",
  "version": "1.2.1",
+ "type": "module",
  "description": "Simple combinatorics like power set, combination, and permutation in JavaScript",
  "main": "combinatorics.js",
  "module": "combinatorics.js",
  "types": "combinatorics.d.ts",

Then the snippet above executes as expected.

Environment info

baseN should return [[]] rather than [] for inputs of length 0

I found this when debugging a corner case in the solve24 module and I was able to trace it down to a call to baseN([], n) which results in [] rather than the expected [[]]. Here's an example to help explain: let's say we want to generate all possible ways to perform some set ops of binary operations (like +,-,*,/) on ops.length+1 numbers. To do so, one might call baseN(ops, ops.length) to find all ops.length-size combinations of ops for applying to the ops.length+1 numbers. However, in the case that ops is empty, there is still exactly one possible combination of ops, namely itself. The current, incorrect behavior of baseN([], n) returning [] should be changed to return [[]] instead.

Lazy filter

Currently, the "filter" function of a generator actually generates all elements. This might take a long time if the power-set is large. Is this possible to add a filter function that returns a filtered generator, such that, the new generator returns filtered elements, but does not generate all elements in advance?

Please add a changelog

It would be great if you could add a changelog that details what's in each update. It can give people more confidence when upgrading.

Wrong combinations

The combination algorithm seems to be buggy since 1.x.

As of 1.3.0 [...new Combination([1, 2, 3, 4, 5], 2)] returns the following array:
[[1,2],[1,3],[1,4],[2,1],[2,3],[3,1],[2,4],[3,2],[3,5],[5,1]]

whereas e.g. Python's itertools library returns the correct
[[1,2],[1,3],[1,4],[1,5],[2,3],[2,4],[2,5],[3,4],[3,5],[4,5]].

baseN size limiter

I'm using baseN to create a 11x11 array.

const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2];
const allCombinations = Combinatorics.baseN(values, 11).toArray();

I'm with the out-of-memory error. So i'm wondering if is there a way to limit my result string to show my only the firsts X results?

Thanks.

baseN start from last

Hi. I'm using baseN to create a combinations from 10 elements(numbers and letters).
I save each array to a file.
Can i somehow start with the last array if I stop generating combinations?

Make available as bower package

I'm requesting that this project be registered as bower.io package. As it is already a node package & meets the naming & versioning requirements, I don't believe there are any major difficulties in getting this done.

I would be happy to submit a PR with bower.json if you agree.

Thanks for your consideration!

Permutation with Repetition

Sorry, I don't know exact English term for this, but that should be know as Permutation with Repetition, total number of permutations is n ^ r.

I have an alphabet, 'a, b, c, d, e' and need to generate all possible permutations, including ones

[a, a, a, a],
[a, b, a, a]
[a, b, c, a]

etc.

Can it be done with js-combinatorics?

Not in npm registry

If you run npm publish from the root project it will be installable direcly from command line without using git.

Strange behavior with using .reduce on Combinatorics.combination

For example, if I were to have:

const Combinatorics = require("js-combinatorics");

const group = ["Penelope", "Brad", "Ivan"];

const cmb = Combinatorics.combination(group, 2);

const pairings = cmb.reduce((acc, pair) => `${acc}${pair[0]} ${pair[1]}, `, "");

console.log(pairings);

The output would be P e, B r, I v, instead of the expected Penelope Brad, Penelope Ivan, Brad Ivan, .

Is there any specific reason why this happens besides that it's just a bug? Other array methods like forEach, filter, map, etc. seem to work fine; just not reduce.

Thanks! 😊

Typescript support

It would be great if this package could gain native typescript support. With the 1.0.0 upgrade, this package has become unusable for me because the type definitions provided by DefinitelyTyped are now out of date. It's easier for me to just switch to another package that's natively written in Typescript, where the type definitions will always be in sync.

Bug with combinations

hello, i guess this is not a normal behavior:

var _ = require('underscore');
var Combinatorics = require('js-combinatorics');

> console.log(Combinatorics.combination(_.range(500), 2).toArray().length)
190
> console.log(Combinatorics.bigCombination(_.range(500), 2).toArray().length)
124750

`permutation` with larger arrays (> 32 entries) issue

permutation() behaves strangely with input arrays with more than 32 entries.

Somehow it seems to only consider the first number of entries as difference from a multiple of 32 - e.g. from an input of 71 entries it only used the first 7 [ 71 - 2 * 32 = 7 ].

Poking around permutation() function and noticing the bigCombination() variant, I found that using that instead of combination() makes it work correctly.

I don't know whether you want to introduce a new bigPermutation() function or maybe just patch existing permutation() function:

var permutation = function(ary, nelem, fun) {
        [...]
        addProperties(that, {
            valueOf: function() {
                return size;
            },
            init: function() {
                this.cmb = (ary.length <= 32 ? combination(ary, nelem) : bigCombination(ary, nelem));
                this.per = _permutation(this.cmb.next());
            },
        [...]
}

for (let byte of baseN([0, 1], 8)) {} does not work

I would like to iterate over the result of baseN.

const baseN = require('js-combinatorics').baseN

for (let [a, b, c, d] of baseN([0, 1], 4)) {
   console.log(a, b, c, d)
}

Also Array.from(baseN([0, 1], 4)) produces a result different from baseN([0, 1], 4).toArray().

I believe that adding a Symbol.iterator property would fix this.

cmb.each is not a function

Doesn't seem to work in node 5.10.1. I ran the module's tests with mocha and that worked, so not sure what the issue is.

$ node --version
v5.10.1
$ node
> var Combinatorics = require('js-combinatorics');
undefined
> var cmb, a;
undefined
> cmb = Combinatorics.power(['a','b','c']);
[Number: 8]
> cmb.each(function(a){ console.log(a) });
TypeError: cmb.each is not a function
    at repl:1:5
    at REPLServer.defaultEval (repl.js:269:27)
    at bound (domain.js:287:14)
    at REPLServer.runBound [as eval] (domain.js:300:12)
    at REPLServer.<anonymous> (repl.js:439:12)
    at emitOne (events.js:95:20)
    at REPLServer.emit (events.js:182:7)
    at REPLServer.Interface._onLine (readline.js:211:10)
    at REPLServer.Interface._line (readline.js:550:8)
    at REPLServer.Interface._ttyWrite (readline.js:827:14)

permutations in pairs

Is there a way for this library to handle permutations in pairs?

const collection = ['a', 'b', 'c', 'd'];
const permutations = Combinatorics.permutation(collection, 2);
a b
a c
a d
b a
b c
......

Any way to handle duplicates?

I have a set of elements with possible duplication.

I need to get all possible permutations of combinations but removing duplicates.

Like so:

const all = ['a', 'a', 'b'];
const cmb = Combinatorics.permutationCombination(all);
console.log(cmb.toArray());

Expected output is:

[ [ 'a' ],
  [ 'b' ],
  [ 'a', 'a' ],
  [ 'a', 'b' ],
  [ 'b', 'a' ],
  [ 'a', 'a', 'b' ],
  [ 'a', 'b', 'a' ],
  [ 'b', 'a', 'a' ] ]

but instead, I am getting

[ [ 'a' ],
  [ 'a' ],
  [ 'b' ],
  [ 'a', 'a' ],
  [ 'a', 'a' ],
  [ 'a', 'b' ],
  [ 'b', 'a' ],
  [ 'a', 'b' ],
  [ 'b', 'a' ],
  [ 'a', 'a', 'b' ],
  [ 'a', 'b', 'a' ],
  [ 'a', 'a', 'b' ],
  [ 'a', 'b', 'a' ],
  [ 'b', 'a', 'a' ],
  [ 'b', 'a', 'a' ] ]

So far I have been able to work around this with a control list and checking while iterating. I could refactor this into a lazyFilter that checks such list, but it would pretty much be the same as I still need to externally keep track of the already visited permutations.

Any way to keep this contained within the library?

RangeError in array with size > 32 in combination

Hi,

I found RangeError problem with combination function when used arrays with size > 32:

var x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]; var array = Combinatorics.combination(x, 2).toArray();

Any idea for solve it?

Thanks for advance.

base N function - uncaught exception: out of memory

I am not able to generate base N for the following combinations.

cmb = Combinatorics.baseN(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "G", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"], 6);
// console.log("All data", cmb.toArray());
//debugger;
console.log("All data", cmb.toArray().slice(1,5));

console.log("done!!");

Getting uncaught exception: out of memory in firefox
image

and tried in node as well using

node --max-old-space-size=7168 index.js

But can't get it to work. Please advice on how can I fetch first N number of records, without performing a toArray.

<--- Last few GCs --->

  292719 ms: Mark-sweep 7067.8 (7202.5) -> 7067.8 (7202.5) MB, 37117.6 / 0 ms [allocation failure] [GC in old space requested].
  331128 ms: Mark-sweep 7067.8 (7202.5) -> 7067.8 (7202.5) MB, 38409.3 / 0 ms [allocation failure] [GC in old space requested].
  370411 ms: Mark-sweep 7067.8 (7202.5) -> 7067.8 (7202.5) MB, 39283.0 / 0 ms [last resort gc].
  406547 ms: Mark-sweep 7067.8 (7202.5) -> 7067.8 (7202.5) MB, 36136.2 / 0 ms [last resort gc].


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x2f681adc9e31 <JS Object>
    1: toArray [/Volumes/256gb/diksha/combination-cli/node_modules/js-combinatorics/combinatorics.js:~67] [pc=0x1b628d0965c9] (this=0x364faa3580d9 <an Object with map 0x2b5341a31289>,f=0x2f681ad04189 <undefined>)
    2: arguments adaptor frame: 0->1
    3: /* anonymous */(aka /* anonymous */) [/Volumes/256gb/diksha/combination-cli/index.js:121] [pc=0x1b628d08f1a8] (this=0x2f681ad04189 <undefine...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: node::Abort() [/Users/Sachdev/.nvm/versions/node/v6.3.1/bin/node]
 2: node::FatalException(v8::Isolate*, v8::Local<v8::Value>, v8::Local<v8::Message>) [/Users/Sachdev/.nvm/versions/node/v6.3.1/bin/node]
 3: v8::internal::V8::FatalProcessOutOfMemory(char const*, bool) [/Users/Sachdev/.nvm/versions/node/v6.3.1/bin/node]
 4: v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [/Users/Sachdev/.nvm/versions/node/v6.3.1/bin/node]
 5: v8::internal::Runtime_AllocateInTargetSpace(int, v8::internal::Object**, v8::internal::Isolate*) [/Users/Sachdev/.nvm/versions/node/v6.3.1/bin/node]
 6: 0x1b628cf06338
 7: 0x1b628d0965c9
[1]    11802 abort      node --max-old-space-size=7168 index.js

Combination with repetition

combinations with repetition are currently not supported, right?
Something like that would be awesome!

Combinatorics.combinationWithRep(['a', 'b', 'c'], 4)
/*
=> [ 
  [ 'a', 'a', 'a', 'a' ],
  [ 'a', 'a', 'a', 'b' ],
  [ 'a', 'a', 'a', 'c' ],
  [ 'a', 'a', 'b', 'b' ],
  [ 'a', 'a', 'b', 'c' ],
  [ 'a', 'a', 'c', 'c' ],
  [ 'a', 'b', 'b', 'b' ],
  [ 'a', 'b', 'b', 'c' ],
  [ 'a', 'b', 'c', 'c' ],
  [ 'a', 'c', 'c', 'c' ],
  [ 'b', 'b', 'b', 'b' ],
  [ 'b', 'b', 'b', 'c' ],
  [ 'b', 'b', 'c', 'c' ],
  [ 'b', 'c', 'c', 'c' ],
  [ 'c', 'c', 'c', 'c' ] 
 ]
*/

I found a bit strange result of Combinatorics.power

Node version is v0.12.4.

I have confirmed following tests.

/*
 * use mocha to test me
 * http://visionmedia.github.com/mocha/
 */
var assert, Combinatorics;
if (this['window'] !== this) {
    assert = require("assert");
    Combinatorics = require('../.');
}
var is_deeply = function (a, e, m) {
    return function () {
        assert.equal(JSON.stringify(a), JSON.stringify(e), m)
    }
};
describe('Combinatorics.power', function () {
    var a = [], c = Combinatorics.power(a);
    it(c, is_deeply(c.toArray(), [
        []
    ]));
    a = 'abc'.split(''), c = Combinatorics.power(a);
    it(a, is_deeply(c.toArray(), [
        [],
        ["a"],
        ["b"],
        ["a", "b"],
        ["c"],
        ["a", "c"],
        ["b", "c"],
        ["a", "b", "c"]
    ]));
    it(0+c, is_deeply(0+c, c.toArray().length));
    it(c.length, is_deeply(c.length, c.toArray().length));
    it(a, is_deeply(c.filter(function(a){return a.length === 2}),
                    Combinatorics.combination(a,2).toArray()
           ));

    a = ["a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9",
         "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9",
         "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9",
         "d0"]
    c = Combinatorics.power(a);
    it(a, is_deeply(c.toArray(), []));

    a = ["a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9",
         "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9",
         "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9",
         "d0", "d1"]
    c = Combinatorics.power(a);
    it(a, is_deeply(c.toArray(), [[]]));
});

I added two tests to test/power.js. But, I think that its two tests should not be passed.

combination(x, 0) returns x

it should return [] instead. i'm not sure if this is mathematically defined, but it might just be an edge worth testing for.

very nice little library, thank you :)

Licence file

Thank you for a great library.
I see in the package.json file you say the licence is BSD but you don't have a licence file in the repository.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.