The function signature predicament
The problem with coming up with a semantic, intuitive, standardized syntax for combinatorial functions in JavaScript
.
The inspiration for this module, itertools, uses the following function signatures
combinations(iterable, r)
The combinations
function is used with an iterable
and an r
. Most of the combinatorial functions from itertools follows this general pattern.
permutations(iterable, r=None)
A slight exception is permutations
which allows r
to not be passed. This is convenient because usually permutations
is used with r
as the length of the iterable
.
product(*iterables, repeat=1)
Here, repeat
- a named keyword argument, is used after Python's variadic iterables
. With JavaScript
this is not possible. Python
's implementation is also less than ideal here because repeat
must be passed using the repeat
name which does not match the syntax of the rest of the itertools functions.
This function provides the functionality of both cartesian products as well as permutations with replacement.
The original combinatorics syntax
combinations(r, iterable)
The difference being that r
is passed before iterable
. In some ways this is an improvement. It sounds nicer - for example - "choose r
from n
" (where n
is the length of iterable
).
This is not true in other ways. The iterable
argument is arguably more important which makes it feel like it should come first.
Another downside is that this does not line up with the itertools syntax.
permutations(r = undefined, iterable)
This is one of the largest downsides. When you don't know the length of iterable
and you want to set r
to be the length of the iterable
you must use undefined
as the first parameter for permutations
. This does not spark joy.
permutations(undefined, [1, 2, 3]);
product(r, ...iterables)
This is the main reason for using r
as the first parameter. The product
function can have a repeat
parameter without sacrificing the convenience of variadic arguments in JavaScript
.
An alternative solution
combinations(iterable, r)
The function signature for combinations matches that of itertools.
permutations(iterable, r = undefined)
The function signature for permutations does as well. This is convenient because r
can be skipped in order to use the length of the iterable
.
product
The product
function no longer exists. Instead, the functionality is replaced with that of cartesianProduct
and permutationsWithReplacement
. One downside is that there is a lot of repeated code between these two functions. There is somewhat of a precedent for this where powerSet
is nearly identical to combinations
.
On the upside, however, this is in line with the Deno
ideology of 'explicit is good'. Where previously product
was presumed to have the functionality of permutationsWithReplacement
it is now obvious.
Another downside is some niche functionality no longer exists. Multiple iterables cannot be provided and repeated like they could with product
.
product(3, [1, 2], [3, 4]);
As far as I know this is fine though because I do not know of a valid use case for this. Also, this can easily be replicated with the following.
const repeat = 3;
const iterables = [[1, 2], [3, 4]];
const repeatedIterables = Array(repeat).fill(iterables).flat();
cartesianProduct(...repeatedIterables);
permutationsWithReplacement(r, iterable)
This could be an improvement upon even itertools where repeat
comes after iterable
. This syntax parallels the other functions.
Another good thing is that without multiple pools, permutationsWithReplacement
receives a small performance boost.
cartesianProduct(...iterables)
The combinatorics module receives another niche function similar to powerSet
.
This function becomes slightly simpler because iterables does not have to be unpacked and re-packed to adjust for the r
parameter.
Other possible function signatures
There are also other possibilities.
Packed product
The iterables
is provided to product
without variadics.
combinations(iterable, r);
product(iterables, r);
Providing such a complex data structure to product
could become messy... This also doesn't exactly match the other function signatures because it is an array of arrays rather than just an array.
product([[1, 2, 3], [4, 5], [6, 7, 8]], r);
Non-homogenous function signatures
Not ideal.
combinations(iterable, r);
product(r, ...iterables);
Conclusion
Version 1.0.0 will likely be released with breaking changes to support improved syntax. These changes currently live in the alternate_parameters
branch.