nthPermutation (#5068)

add the function nthPermutation that permutates a given range in place n
permutations in O(1).

This is O(n - 1) steps faster than calling nextPermutation n times.
This commit is contained in:
Robert Schadek 2018-11-12 11:33:28 +00:00 committed by Nicholas Wilson
parent 3b601459d6
commit a05af05d95
2 changed files with 258 additions and 0 deletions

View file

@ -35,6 +35,9 @@ $(T2 nextEvenPermutation,
$(T2 nextPermutation,
Computes the next lexicographically greater permutation of a range
in-place.)
$(T2 nthPermutation,
Computes the nth permutation of a range
in-place.)
$(T2 partialSort,
If `a = [5, 4, 3, 2, 1]`, then `partialSort(a, 3)` leaves
`a[0 .. 3] = [1, 2, 3]`.
@ -4466,3 +4469,175 @@ shapes. Here's a non-trivial example:
}
assert(n == 60);
}
/** Permutes `range` into the `perm` permutation.
The algorithm has a constant runtime complexity with respect to the number of
permutations created.
Due to the number of unique values of `ulong` only the first 21 elements of
`range` can be permuted. The rest of the range will therefore not be
permuted.
This algorithm uses the $(HTTP en.wikipedia.org/wiki/Lehmer_code, Lehmer
Code).
The algorithm works as follows:
$(D_CODE
auto pem = [4,0,4,1,0,0,0]; // permutation 2982 in factorial
auto src = [0,1,2,3,4,5,6]; // the range to permutate
auto i = 0; // range index
// range index iterates pem and src in sync
// pem[i] + i is used as index into src
// first src[pem[i] + i] is stored in t
auto t = 4; // tmp value
src = [0,1,2,3,n,5,6];
// then the values between i and pem[i] + i are moved one
// to the right
src = [n,0,1,2,3,5,6];
// at last t is inserted into position i
src = [4,0,1,2,3,5,6];
// finally i is incremented
++i;
// this process is repeated while i < pem.length
t = 0;
src = [4,n,1,2,3,5,6];
src = [4,0,1,2,3,5,6];
++i;
t = 6;
src = [4,0,1,2,3,5,n];
src = [4,0,n,1,2,3,5];
src = [4,0,6,1,2,3,5];
)
Returns:
The permuted range.
Params:
range = The Range to permute. The original ordering will be lost.
perm = The permutation to permutate `range` to.
*/
auto ref Range nthPermutation(Range)
(auto ref Range range, const ulong perm)
if (isRandomAccessRange!Range && hasLength!Range)
{
if (!nthPermutationImpl(range, perm))
{
throw new Exception(
"The range to permutate must not have less"
~ " elements than the factorial number has digits");
}
return range;
}
///
pure @safe unittest
{
auto src = [0, 1, 2, 3, 4, 5, 6];
auto rslt = [4, 0, 6, 2, 1, 3, 5];
src = nthPermutation(src, 2982);
assert(src == rslt);
}
/**
Returns: `true` in case the permutation worked, `false` in case `perm` had
more digits in the factorial number system than range had elements.
This case must not occur as this would lead to out of range accesses.
*/
bool nthPermutationImpl(Range)
(auto ref Range range, ulong perm)
if (isRandomAccessRange!Range && hasLength!Range)
{
import std.range.primitives : ElementType;
import std.numeric : decimalToFactorial;
// ulong.max has 21 digits in the factorial number system
ubyte[21] fac;
size_t idx = decimalToFactorial(perm, fac);
if (idx > range.length)
{
return false;
}
ElementType!Range tmp;
size_t i = 0;
for (; i < idx; ++i)
{
size_t re = fac[i];
tmp = range[re + i];
for (size_t j = re + i; j > i; --j)
{
range[j] = range[j - 1];
}
range[i] = tmp;
}
return true;
}
///
pure @safe unittest
{
auto src = [0, 1, 2, 3, 4, 5, 6];
auto rslt = [4, 0, 6, 2, 1, 3, 5];
bool worked = nthPermutationImpl(src, 2982);
assert(worked);
assert(src == rslt);
}
pure @safe unittest
{
auto rslt = [4, 0, 6, 2, 1, 3, 5];
auto src = nthPermutation([0, 1, 2, 3, 4, 5, 6], 2982);
assert(src == rslt);
}
pure @safe unittest
{
auto src = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
auto rslt = [4, 0, 6, 2, 1, 3, 5, 7, 8, 9, 10];
src = nthPermutation(src, 2982);
assert(src == rslt);
}
pure @safe unittest
{
import std.exception : assertThrown;
auto src = [0, 1, 2, 3];
assertThrown(nthPermutation(src, 2982));
}
pure @safe unittest
{
import std.internal.test.dummyrange;
import std.meta : AliasSeq;
auto src = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
auto rsl = [4, 0, 6, 2, 1, 3, 5, 7, 8, 9, 10];
foreach (T; AliasSeq!(
DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, int[]),
DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, int[])))
{
static assert(isRandomAccessRange!(T));
static assert(hasLength!(T));
auto dr = T(src.dup);
dr = nthPermutation(dr, 2982);
int idx;
foreach (it; dr)
{
assert(it == rsl[idx++]);
}
}
}