Itertools.combinations in Javascript

I like itertools.combinations and wanted a memory-efficient JavaScript generator function, but couldn't quickly find an acceptable library so I rolled my own.

It's in TypeScript (to help me keep track of bookkeeping), but I'll append the transpiled JavaScript at the bottom.

function* range(start: number, end: number) {
  for (; start <= end; ++start) { yield start; }
}

function last<T>(arr: T[]) { return arr[arr.length - 1]; }

function* numericCombinations(n: number, r: number, loc: number[] = []): IterableIterator<number[]> {
  const idx = loc.length;
  if (idx === r) {
    yield loc;
    return;
  }
  for (let next of range(idx ? last(loc) + 1 : 0, n - r + idx)) { yield* numericCombinations(n, r, loc.concat(next)); }
}

function* combinations<T>(arr: T[], r: number) {
  for (let idxs of numericCombinations(arr.length, r)) { yield idxs.map(i => arr[i]); }
}

All the black magic is in the numericCombinations function, which is a recursive generator—see MDN documentation on yield*. The actual combinations function just wraps it to match the Python API.

I can put this in a .ts file and put this at the bottom of it:

if (module === require.main) {
  const shorts = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
  let i = 0;
  for (let o of combinations(shorts, 7)) { i++; }
  console.log(i);
}

and Node prints out 133784560 in less than 2.5 minutes, with minimal memory usage, on my 2015-vintage laptop. That is, it generated all hundred thirty-four million ways you can choose seven cards from a standard deck of fifty-two playing cards (i.e., all complete Texas hold ’em hands) without burdening memory or overly-nesting recursive function calls.

(Python3 can do this in 20 seconds, or 7× faster… speedups to the above welcome.)


JavaScript code:

function* range(start, end) {
  for (; start <= end; ++start) { yield start; }
}
function last(arr) { return arr[arr.length - 1]; }
function* numericCombinations(n, r, loc = []) {
  const idx = loc.length;
  if (idx === r) {
    yield loc;
    return;
  }
  for (let next of range(idx ? last(loc) + 1 : 0, n - r + idx)) { yield* numericCombinations(n, r, loc.concat(next)); }
}
function* combinations(arr, r) {
  for (let idxs of numericCombinations(arr.length, r)) { yield idxs.map(i => arr[i]); }
}

You could use a recursive approach for gettign the permutation of the given array with a specified size.

function getPermutations(array, size) {

    function p(t, i) {
        if (t.length === size) {
            result.push(t);
            return;
        }
        if (i + 1 > array.length) {
            return;
        }
        p(t.concat(array[i]), i + 1);
        p(t, i + 1);
    }

    var result = [];
    p([], 0);
    return result;
}

var array = ['a', 'b', 'c', 'd'];

console.log(getPermutations(array, 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use my es-iter library, which is almost one to one port of Python's itertools but in JS way.

https://github.com/abozhilov/ES-Iter#combinationsr

Tags:

Javascript