Skip to main content
2 of 7
added 1 character in body

Can two equivalent JavaScript generators be derived from each other?

I have two JavaScript generator functions for permutations and I would like to understand their relative merits.

From Robert Sedgewick's paper, Permutation Generation Methods (1977), we obtain the basic recursive algorithm on page 140:

To generate all permutations of P[1], • • •, P[N], we repeat N times the step: "first generate all permutations of P[1], •••, P[N-1], then exchange P[N] with one of the elements P[1], •••, P[N-1]."

A first JavaScript generator function can be derived like this:

function* permutations(n) {
    if (n == 1) yield P;
    else {
        for (let i = 1; i <= n; i++) {
            yield* permutations(n - 1);
            if (i < n) swap(n, heap(n, i));
        }
    }
}

Note that I have added the obvious recursion base case n==1 and a routinely chosen position of the yield statement. Also, to avoid "unnecessary" swaps during the last iteration of the for loop, there is the guard if (i < n). In this way, it seems to be equivalent to Heap's recursive algorithm given in Wikipedia. In any case, when called for N = 4 it reproduces Heap's enumeration as given in Sedgewick's paper.

The following code is needed to run the generator:

var P = [], N, count; 

function swap(x, y) { let t = P[x]; P[x] = P[y]; P[y] = t }
function heap(n, i) { return (n % 2) || i }

N = 4;
for (let i = 1; i <= N; i++) P[i] = i;
count = 1;
for (p of permutations(N)) console.log(count++, ":", p.slice(1));

The other generator is this one:

function* permutations(n) {
    if (n > 1) {
        for (let i = 1; i <= n; i++) {
            yield* permutations(n - 1);
            if (i < n) { swap(n, heap(n, i)); yield P }
        }
    }
}

The main difference is the position of the yield statement, which was found "by inspection" (hard thinking), and the (apparently) missing base case, because it simply returns and does nothing.

This generator returns the same sequence of permutations as the first one, with one exception: the initially given permutation is not yielded, but must be treated on its own.

Is there one rigorous way to formally "deduce" or "derive" one generator from the other? This is in view of the fact that they are both "the same" (more or less). Is one of them to be preferred over the other?