Skip to content

Commit c5f2891

Browse files
authored
Merge pull request #4809 from andralex/pivotPartition
Add pivotPartition
2 parents b297804 + a9b7fb0 commit c5f2891

File tree

1 file changed

+201
-3
lines changed

1 file changed

+201
-3
lines changed

std/algorithm/sorting.d

Lines changed: 201 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ $(T2 partialSort,
4040
$(D a[0 .. 3] = [1, 2, 3]).
4141
The other elements of $(D a) are left in an unspecified order.)
4242
$(T2 partition,
43-
Partitions a range according to a predicate.)
43+
Partitions a range according to a unary predicate.)
4444
$(T2 partition3,
45-
Partitions a range in three parts (less than, equal, greater than the
46-
given pivot).)
45+
Partitions a range according to a binary predicate in three parts (less
46+
than, equal, greater than the given pivot). Pivot is not given as an
47+
index, but instead as an element independent from the range's content.)
48+
$(T2 pivotPartition,
49+
Partitions a range according to a binary predicate in two parts: less
50+
than or equal, and greater than or equal to the given pivot, passed as
51+
an index in the range.)
4752
$(T2 schwartzSort,
4853
Sorts with the help of the $(LUCKY Schwartzian transform).)
4954
$(T2 sort,
@@ -563,6 +568,197 @@ Range partition(alias predicate, SwapStrategy ss = SwapStrategy.unstable, Range)
563568
assert(isPartitioned!`a.length < 5`(b));
564569
}
565570

571+
// pivotPartition
572+
/**
573+
574+
Partitions `r` around `pivot` using comparison function `less`, algorithm akin
575+
to $(LUCKY Hoare partition). Specifically, permutes elements of `r` and returns
576+
an index $(D k < r.length) such that:
577+
578+
$(UL
579+
580+
$(LI `r[pivot]` is swapped to `r[k]`)
581+
582+
$(LI All elements `e` in subrange $(D r[0 .. k]) satisfy $(D !less(r[k], e))
583+
(i.e. `r[k]` is greater than or equal to each element to its left according to
584+
predicate `less`))
585+
586+
$(LI All elements `e` in subrange $(D r[0 .. k]) satisfy $(D !less(e,
587+
r[k])) (i.e. `r[k]` is less than or equal to each element to its right
588+
according to predicate `less`)))
589+
590+
If `r` contains equivalent elements, multiple permutations of `r` satisfy these
591+
constraints. In such cases, `pivotPartition` attempts to distribute equivalent
592+
elements fairly to the left and right of `k` such that `k` stays close to $(D
593+
r.length / 2).
594+
595+
Params:
596+
less = The predicate used for comparison, modeled as a $(LUCKY strict weak
597+
ordering) (irreflexive, antisymmetric, transitive, and implying a transitive
598+
equivalence)
599+
r = The range being partitioned
600+
pivot = The index of the pivot for partitioning, must be less than `r.length` or
601+
`0` is `r.length` is `0`
602+
603+
Returns:
604+
The new position of the pivot
605+
606+
See_Also:
607+
$(HTTP jgrcs.info/index.php/jgrcs/article/view/142, Engineering of a Quicksort
608+
Partitioning Algorithm), D. Abhyankar, Journal of Global Research in Computer
609+
Science, February 2011. $(HTTPS youtube.com/watch?v=AxnotgLql0k, ACCU 2016
610+
Keynote), Andrei Alexandrescu.
611+
*/
612+
size_t pivotPartition(alias less = "a < b", Range)
613+
(Range r, size_t pivot)
614+
if (isRandomAccessRange!Range && hasLength!Range && hasSlicing!Range)
615+
{
616+
assert(pivot < r.length || r.length == 0 && pivot == 0);
617+
if (r.length <= 1) return 0;
618+
import std.algorithm.mutation : swapAt, move;
619+
alias lt = binaryFun!less;
620+
621+
// Pivot at the front
622+
r.swapAt(pivot, 0);
623+
624+
// Fork implemnentation depending on nothrow copy, assignment, and
625+
// comparison. If all of these are nothrow, use the specialized
626+
// implementation discussed at https://youtube.com/watch?v=AxnotgLql0k.
627+
static if (is(typeof(
628+
() nothrow { auto x = r.front; x = r.front; return lt(x, x); }
629+
)))
630+
{
631+
auto p = r[0];
632+
// Plant the pivot in the end as well as a sentinel
633+
size_t lo = 0, hi = r.length - 1;
634+
auto save = move(r[hi]);
635+
r[hi] = p; // Vacancy is in r[$ - 1] now
636+
// Start process
637+
for (;;)
638+
{
639+
// Loop invariant
640+
version(unittest)
641+
{
642+
import std.algorithm.searching;
643+
assert(r[0 .. lo].all!(x => x <= p));
644+
assert(r[hi + 1 .. $].all!(x => x >= p));
645+
}
646+
do ++lo; while (lt(r[lo], p));
647+
r[hi] = r[lo];
648+
// Vacancy is now in r[lo]
649+
do --hi; while (lt(p, r[hi]));
650+
if (lo >= hi) break;
651+
r[lo] = r[hi];
652+
// Vacancy is not in r[hi]
653+
}
654+
// Fixup
655+
assert(lo - hi <= 2);
656+
assert(!lt(p, r[hi]));
657+
if (lo == hi + 2)
658+
{
659+
assert(!lt(r[hi + 1], p));
660+
r[lo] = r[hi + 1];
661+
--lo;
662+
}
663+
r[lo] = save;
664+
if (lt(p, save)) --lo;
665+
assert(!lt(p, r[lo]));
666+
}
667+
else
668+
{
669+
size_t lo = 1, hi = r.length - 1;
670+
loop: for (;; lo++, hi--)
671+
{
672+
for (;; ++lo)
673+
{
674+
if (lo > hi) break loop;
675+
if (!lt(r[lo], r[0])) break;
676+
}
677+
// found the left bound: r[lo] >= r[0]
678+
assert(lo <= hi);
679+
for (;; --hi)
680+
{
681+
if (lo >= hi) break loop;
682+
if (!lt(r[0], r[hi])) break;
683+
}
684+
// found the right bound: r[hi] <= r[0], swap & make progress
685+
assert(!lt(r[lo], r[hi]));
686+
r.swapAt(lo, hi);
687+
}
688+
--lo;
689+
}
690+
r.swapAt(lo, 0);
691+
return lo;
692+
}
693+
694+
///
695+
@safe nothrow unittest
696+
{
697+
int[] a = [5, 3, 2, 6, 4, 1, 3, 7];
698+
size_t pivot = pivotPartition(a, a.length / 2);
699+
import std.algorithm.searching : all;
700+
assert(a[0 .. pivot].all!(x => x <= a[pivot]));
701+
assert(a[pivot .. $].all!(x => x >= a[pivot]));
702+
}
703+
704+
@safe unittest
705+
{
706+
void test(alias less)()
707+
{
708+
int[] a;
709+
size_t pivot;
710+
711+
a = [-9, -4, -2, -2, 9];
712+
pivot = pivotPartition!less(a, a.length / 2);
713+
import std.algorithm.searching : all;
714+
assert(a[0 .. pivot].all!(x => x <= a[pivot]));
715+
assert(a[pivot .. $].all!(x => x >= a[pivot]));
716+
717+
a = [9, 2, 8, -5, 5, 4, -8, -4, 9];
718+
pivot = pivotPartition!less(a, a.length / 2);
719+
assert(a[0 .. pivot].all!(x => x <= a[pivot]));
720+
assert(a[pivot .. $].all!(x => x >= a[pivot]));
721+
722+
a = [ 42 ];
723+
pivot = pivotPartition!less(a, a.length / 2);
724+
assert(pivot == 0);
725+
assert(a == [ 42 ]);
726+
727+
a = [ 43, 42 ];
728+
pivot = pivotPartition!less(a, 0);
729+
assert(pivot == 1);
730+
assert(a == [ 42, 43 ]);
731+
732+
a = [ 43, 42 ];
733+
pivot = pivotPartition!less(a, 1);
734+
assert(pivot == 0);
735+
assert(a == [ 42, 43 ]);
736+
737+
a = [ 42, 42 ];
738+
pivot = pivotPartition!less(a, 0);
739+
assert(pivot == 0 || pivot == 1);
740+
assert(a == [ 42, 42 ]);
741+
pivot = pivotPartition!less(a, 1);
742+
assert(pivot == 0 || pivot == 1);
743+
assert(a == [ 42, 42 ]);
744+
745+
import std.random : uniform;
746+
import std.algorithm.iteration : map;
747+
a = iota(0, uniform(1, 1000)).map!(_ => uniform(-1000, 1000)).array;
748+
pivot = pivotPartition!less(a, a.length / 2);
749+
assert(a[0 .. pivot].all!(x => x <= a[pivot]));
750+
assert(a[pivot .. $].all!(x => x >= a[pivot]));
751+
}
752+
test!"a < b";
753+
static bool myLess(int a, int b)
754+
{
755+
static bool bogus;
756+
if (bogus) throw new Exception(""); // just to make it no-nothrow
757+
return a < b;
758+
}
759+
test!myLess;
760+
}
761+
566762
/**
567763
Params:
568764
pred = The predicate that the range should be partitioned by.
@@ -2625,6 +2821,7 @@ schwartzSort(alias transform, alias less = "a < b",
26252821
arr[2] = highEnt;
26262822

26272823
schwartzSort!(entropy, q{a > b})(arr);
2824+
26282825
assert(arr[0] == highEnt);
26292826
assert(arr[1] == midEnt);
26302827
assert(arr[2] == lowEnt);
@@ -2658,6 +2855,7 @@ schwartzSort(alias transform, alias less = "a < b",
26582855
arr[2] = highEnt;
26592856

26602857
schwartzSort!(entropy, q{a < b})(arr);
2858+
26612859
assert(arr[0] == lowEnt);
26622860
assert(arr[1] == midEnt);
26632861
assert(arr[2] == highEnt);

0 commit comments

Comments
 (0)