mirror of
https://github.com/dlang/phobos.git
synced 2025-05-03 00:20:26 +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,
|
$(T2 nextPermutation,
|
||||||
Computes the next lexicographically greater permutation of a range
|
Computes the next lexicographically greater permutation of a range
|
||||||
in-place.)
|
in-place.)
|
||||||
|
$(T2 nthPermutation,
|
||||||
|
Computes the nth permutation of a range
|
||||||
|
in-place.)
|
||||||
$(T2 partialSort,
|
$(T2 partialSort,
|
||||||
If `a = [5, 4, 3, 2, 1]`, then `partialSort(a, 3)` leaves
|
If `a = [5, 4, 3, 2, 1]`, then `partialSort(a, 3)` leaves
|
||||||
`a[0 .. 3] = [1, 2, 3]`.
|
`a[0 .. 3] = [1, 2, 3]`.
|
||||||
|
@ -4466,3 +4469,175 @@ shapes. Here's a non-trivial example:
|
||||||
}
|
}
|
||||||
assert(n == 60);
|
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++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3335,6 +3335,89 @@ C swapRealImag(C)(C input)
|
||||||
return C(input.im, input.re);
|
return C(input.im, input.re);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** This function transforms `decimal` value into a value in the factorial number
|
||||||
|
system stored in `fac`.
|
||||||
|
|
||||||
|
A factorial number is constructed as:
|
||||||
|
$(D fac[0] * 0! + fac[1] * 1! + ... fac[20] * 20!)
|
||||||
|
|
||||||
|
Params:
|
||||||
|
decimal = The decimal value to convert into the factorial number system.
|
||||||
|
fac = The array to store the factorial number. The array is of size 21 as
|
||||||
|
`ulong.max` requires 21 digits in the factorial number system.
|
||||||
|
Returns:
|
||||||
|
A variable storing the number of digits of the factorial number stored in
|
||||||
|
`fac`.
|
||||||
|
*/
|
||||||
|
size_t decimalToFactorial(ulong decimal, ref ubyte[21] fac)
|
||||||
|
@safe pure nothrow @nogc
|
||||||
|
{
|
||||||
|
import std.algorithm.mutation : reverse;
|
||||||
|
size_t idx;
|
||||||
|
|
||||||
|
for (ulong i = 1; decimal != 0; ++i)
|
||||||
|
{
|
||||||
|
auto temp = decimal % i;
|
||||||
|
decimal /= i;
|
||||||
|
fac[idx++] = cast(ubyte)(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx == 0)
|
||||||
|
{
|
||||||
|
fac[idx++] = cast(ubyte) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse(fac[0 .. idx]);
|
||||||
|
|
||||||
|
// first digit of the number in factorial will always be zero
|
||||||
|
assert(fac[idx - 1] == 0);
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@safe pure @nogc unittest
|
||||||
|
{
|
||||||
|
ubyte[21] fac;
|
||||||
|
size_t idx = decimalToFactorial(2982, fac);
|
||||||
|
|
||||||
|
assert(fac[0] == 4);
|
||||||
|
assert(fac[1] == 0);
|
||||||
|
assert(fac[2] == 4);
|
||||||
|
assert(fac[3] == 1);
|
||||||
|
assert(fac[4] == 0);
|
||||||
|
assert(fac[5] == 0);
|
||||||
|
assert(fac[6] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe pure unittest
|
||||||
|
{
|
||||||
|
ubyte[21] fac;
|
||||||
|
size_t idx = decimalToFactorial(0UL, fac);
|
||||||
|
assert(idx == 1);
|
||||||
|
assert(fac[0] == 0);
|
||||||
|
|
||||||
|
fac[] = 0;
|
||||||
|
idx = 0;
|
||||||
|
idx = decimalToFactorial(ulong.max, fac);
|
||||||
|
assert(idx == 21);
|
||||||
|
auto t = [7, 11, 12, 4, 3, 15, 3, 5, 3, 5, 0, 8, 3, 5, 0, 0, 0, 2, 1, 1, 0];
|
||||||
|
foreach (i, it; fac[0 .. 21])
|
||||||
|
{
|
||||||
|
assert(it == t[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fac[] = 0;
|
||||||
|
idx = decimalToFactorial(2982, fac);
|
||||||
|
|
||||||
|
assert(idx == 7);
|
||||||
|
t = [4, 0, 4, 1, 0, 0, 0];
|
||||||
|
foreach (i, it; fac[0 .. idx])
|
||||||
|
{
|
||||||
|
assert(it == t[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The reasons I couldn't use std.algorithm were b/c its stride length isn't
|
// The reasons I couldn't use std.algorithm were b/c its stride length isn't
|
||||||
// modifiable on the fly and because range has grown some performance hacks
|
// modifiable on the fly and because range has grown some performance hacks
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue