mirror of
https://github.com/dlang/phobos.git
synced 2025-04-29 14:40:30 +03:00
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:
parent
3b601459d6
commit
a05af05d95
2 changed files with 258 additions and 0 deletions
|
@ -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++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue