@@ -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/**
567763Params:
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