next_permutation and cartesian product for ranges?

bearophile bearophileHUGS at lycos.com
Tue Oct 9 14:23:07 PDT 2012


Andrei Alexandrescu:

> Yes, we need next_permutation. It will be up to you to convince 
> the Grand Inquisition Committee of Reviewers that 
> next_even_permutation is necessary and/or sufficient.

I don't like the design of C++ STL here. Instead of a 
next_permutation(), what about ranges that yield all 
permutations, all adjacent permutations, all combinations?


Permutations:

import std.algorithm, std.conv, std.traits;

struct Permutations(bool doCopy=true, T) if (isMutable!T) {
     private immutable size_t num;
     private T[] items;
     private uint[31] indexes;
     private ulong tot;

     this (/*in*/ T[] items) /*pure*/ nothrow
     in {
         static enum string L = text(indexes.length); // impure
         assert(items.length >= 0 && items.length <= 
indexes.length,
                "Permutations: items.length must be >= 0 && < " ~ 
L);
     } body {
         static ulong factorial(in uint n) pure nothrow {
             ulong result = 1;
             foreach (i; 2 .. n + 1)
                 result *= i;
             return result;
         }

         this.num = items.length;
         this.items = items.dup;
         foreach (i; 0 .. cast(typeof(indexes[0]))this.num)
             this.indexes[i] = i;
         this.tot = factorial(this.num);
     }

     @property T[] front() pure nothrow {
         static if (doCopy)
             //return items.dup; // not nothrow
             return items ~ []; // slower
         else
             return items;
     }

     @property bool empty() const pure nothrow {
         return tot == 0;
     }

     void popFront() pure nothrow {
         tot--;
         if (tot > 0) {
             size_t j = num - 2;

             while (indexes[j] > indexes[j + 1])
                 j--;
             size_t k = num - 1;
             while (indexes[j] > indexes[k])
                 k--;
             swap(indexes[k], indexes[j]);
             swap(items[k], items[j]);

             size_t r = num - 1;
             size_t s = j + 1;
             while (r > s) {
                 swap(indexes[s], indexes[r]);
                 swap(items[s], items[r]);
                 r--;
                 s++;
             }
         }
     }
}

Permutations!(doCopy,T) permutations(bool doCopy=true, T)
                                     (/*in*/ T[] items)
/*pure*/ nothrow if (isMutable!T) {
     return Permutations!(doCopy, T)(items);
} unittest {
     import std.bigint;
     foreach (p; permutations([BigInt(1), BigInt(2), BigInt(3)]))
         assert((p[0] + 1) > 0);
}

version (permutations2_main) {
     void main() {
         import std.stdio, std.bigint;
         foreach (p; permutations!false([1, 2, 3]))
             writeln(p);
         alias BigInt B;
         foreach (p; permutations!false([B(1), B(2), B(3)])) {}
     }
}

----------------------------------

Adjacent permutations by swapping (to be converted in a range):

import std.algorithm, std.array, std.typecons, std.range;

struct Spermutations {
     const int n;
     alias Tuple!(int[], int) TResult;

     int opApply(int delegate(ref TResult) dg) {
         int result;
         TResult aux;

         int sign = 1;
         alias Tuple!(int, int) Pair;
         auto p = iota(n).map!(i => Pair(i, i ? -1 : 0))().array();

         aux = tuple(p.map!(pp => pp[0])().array(), sign);
         result = dg(aux); if (result) goto END;

         while (p.canFind!(pp => pp[1])()) {
             // Failed using std.algorithm here, too much complex
             auto largest = Pair(-100, -100);
             int i1 = -1;
             foreach (i, pp; p)
                 if (pp[1]) {
                     if (pp[0] > largest[0]) {
                         i1 = i;
                         largest = pp;
                     }
                 }
             int n1 = largest[0], d1 = largest[1];

             sign *= -1;
             int i2;
             if (d1 == -1) {
                 i2 = i1 - 1;
                 swap(p[i1], p[i2]);
                 if (i2 == 0 || p[i2 - 1][0] > n1)
                     p[i2][1] = 0;
             } else if (d1 == 1) {
                 i2 = i1 + 1;
                 swap(p[i1], p[i2]);
                 if (i2 == n - 1 || p[i2 + 1][0] > n1)
                     p[i2][1] = 0;
             }
             aux = tuple(p.map!(pp => pp[0])().array(), sign);
             result = dg(aux); if (result) goto END;

             foreach (i3, ref pp; p) {
                 auto n3 = pp[0], d3 = pp[1];
                 if (n3 > n1)
                     pp[1] = (i3 < i2) ? 1 : -1;
             }
         }

         END: return result;
     }
}

void main() {
     import std.stdio;
     foreach (n; [3, 4]) {
         writefln("\nPermutations and sign of %d items", n);
         foreach (tp; Spermutations(n))
             writefln("Perm: %s Sign: %2d", tp.tupleof);
     }
}

----------------------------------

Combinations (to be converted in a range):

ulong binomial(long n, long k) pure nothrow
in {
     assert(n > 0, "binomial: n must be > 0.");
} body {
     if (k < 0 || k > n)
         return 0;
     if (k > (n / 2))
         k = n - k;
     ulong result = 1;
     foreach (ulong d; 1 .. k + 1) {
         result *= n;
         n--;
         result /= d;
     }
     return result;
}


struct Combinations(T, bool copy=true) {
     // Algorithm by Knuth, Pre-fascicle 3A, draft of
     // section 7.2.1.3: "Generating all partitions".
     T[] items;
     int k;
     size_t len = -1; // computed lazily

     this(in T[] items, in int k)
     in {
         assert(items.length, "combinations: items can't be 
empty.");
     } body {
         this.items = items.dup;
         this.k = k;
     }

     @property size_t length() /*logic_const*/ {
         if (len == -1) // set cache
             len = cast(size_t)binomial(items.length, k);
         return len;
     }

     int opApply(int delegate(ref T[]) dg) {
         if (k == items.length)
             return dg(items);         // yield items

         auto outarr = new T[k];
         if (k == 0)
             return dg(outarr);        // yield []

         if (k < 0 || k > items.length)
             return 0;                 // yield nothing

         int result, x;
         immutable n = items.length;
         auto c = new uint[k + 3]; // c[0] isn'k used

         foreach (j; 1 .. k + 1)
             c[j] = j - 1;
         c[k + 1] = n;
         c[k + 2] = 0;
         int j = k;

         while (true) {
             // The following lines equal to:
             //int pos;
             //foreach (i; 1 .. k +1)
             //    outarr[pos++] = items[c[i]];
             auto outarr_ptr = outarr.ptr;
             auto c_ptr = &(c[1]);
             auto c_ptrkp1 = &(c[k + 1]);
             while (c_ptr != c_ptrkp1)
                 *outarr_ptr++ = items[*c_ptr++];


             static if (copy) {
                 auto outarr2 = outarr.dup;
                 result = dg(outarr2); // yield outarr2
             } else {
                 result = dg(outarr); // yield outarr
             }

             if (j > 0) {
                 x = j;
                 c[j] = x;
                 j--;
                 continue;
             }

             if ((c[1] + 1) < c[2]) {
                 c[1]++;
                 continue;
             } else
                 j = 2;

             while (true) {
                 c[j - 1] = j - 2;
                 x = c[j] + 1;
                 if (x == c[j + 1])
                     j++;
                 else
                     break;
             }

             if (j > k)
                 return result; // End

             c[j] = x;
             j--;
         }
     }
}

Combinations!(T,copy) combinations(bool copy=true, T)
                                   (in T[] items, in int k)
in {
     assert(items.length, "combinations: items can't be empty.");
} body {
     return Combinations!(T, copy)(items, k);
}

----------------------------------

Bye,
bearophile


More information about the Digitalmars-d mailing list