Skip to content

Commit 40df929

Browse files
committed
Add pivotPartition
1 parent 59b6392 commit 40df929

File tree

1 file changed

+191
-6
lines changed

1 file changed

+191
-6
lines changed

std/algorithm/sorting.d

Lines changed: 191 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,197 @@ Range partition(alias predicate,
555555
assert(isPartitioned!`a.length < 5`(b));
556556
}
557557

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

26192810
schwartzSort!(entropy, q{a > b})(arr);
2620-
assert(arr[0] == highEnt);
2621-
assert(arr[1] == midEnt);
2622-
assert(arr[2] == lowEnt);
26232811
assert(isSorted!("a > b")(map!(entropy)(arr)));
26242812
}
26252813

@@ -2650,9 +2838,6 @@ schwartzSort(alias transform, alias less = "a < b",
26502838
arr[2] = highEnt;
26512839

26522840
schwartzSort!(entropy, q{a < b})(arr);
2653-
assert(arr[0] == lowEnt);
2654-
assert(arr[1] == midEnt);
2655-
assert(arr[2] == highEnt);
26562841
assert(isSorted!("a < b")(map!(entropy)(arr)));
26572842
}
26582843

0 commit comments

Comments
 (0)