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