nextPermutation and ranges
bearophile
bearophileHUGS at lycos.com
Thu Feb 7 10:22:10 PST 2013
Recently "quickfur" and Andrei have added C++-style functions
(nextPermutation and nextEvenPermutation) to std.algorithm to
perform permutations, this is a Phobos improvement and I've
already used them few times:
https://github.com/D-Programming-Language/phobos/compare/857f1ed87593...61d26e7dcf2f
Such functions take in account duplications (this is useful), and
require the items to be comparable.
But in many cases I have a set of items, and I want to find (most
or all of) their permutations lazily (even if they are not
comparable). In many of such cases I prefer a permutations
generator that plays well with ranges:
auto result = items
.permutations()
.filter!pred()
.map!foo();
I have other cases in both Python and D where having a lazy
permutations (or combinations) generator/range is handy.
A simple version of such range:
http://rosettacode.org/wiki/Permutations#Fast_Lazy_Version
- - - - - - - - - - - - -
A simple speed benchmark seems to show that a permutations Range
is not bad (0.90 seconds for the range versus 2.76 seconds for
nextPermutation to fully permute 11 integers):
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.
auto items2 = new T[items.length];
items2[] = items;
return items2;
} 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)
(T[] items)
if (isMutable!T) {
return Permutations!(doCopy, T)(items);
}
auto perms1(T)(T[] items) {
foreach (p; permutations!false(items)) {}
return items;
}
auto perms2(T)(T[] items) {
while (nextPermutation(items)) {}
return items;
}
int main() {
auto data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
perms1(data); // 0.90 seconds
//perms2(data); // 2.76 seconds
return data.length;
}
D code compiled with dmd 2.062alpha, -O -release -inline
-noboundscheck.
- - - - - - - - - - - - -
Extra note: maybe all such functions should be moved inside a
std.combinatorics or std.math.comb module or something similar,
with combinations range, binomial coefficient function, etc.
Another handy range is one that yields size_t[2] or
Tuple!(size_t,size_t) that represent the swaps to compute all
ajacent permutations (this code is not a range yet):
http://rosettacode.org/wiki/Permutations_by_swapping#D
Bye,
bearophile
More information about the Digitalmars-d
mailing list