1 /** Extend std.algorithm.setopts to also operate on set- and map-like
2     containers/ranges.
3 
4     See_Also: http://forum.dlang.org/post/nvd09v$24e9$1@digitalmars.com
5 */
6 module nxt.setops_ex;
7 
8 // version = show;
9 
10 /** Specialization for `std.algorithm.setopts.setUnion` for AA. */
11 auto setUnionUpdate(T1, T2)(T1 a, T2 b) @trusted
12 if (isAA!T1 &&
13     isAA!T2)
14 {
15     if (a.length < b.length)
16         return setUnionHelper(a, b);
17     else
18         return setUnionHelper(b, a);
19 }
20 
21 /** Helper function for `setUnionUpdate` that assumes `small` has shorter length than
22     `large` .
23 */
24 private static auto setUnionHelper(Small, Large)(const Small small, Large large)
25 {
26     Large united = large.dup;   // TODO: this shallow copy prevents large from being `const`
27     foreach (const ref e; small.byKeyValue)
28     {
29         if (auto hitPtr = e.key in large)
30             (*hitPtr) = e.value; // TODO: this potentially changes the value of
31         else
32             united[e.key] = e.value;
33     }
34     return united;
35 }
36 
37 /** Is `true` iff `T` is map-like container, that is provides membership
38  * checking via the `in` operator or `contains`.
39  *
40  * TODO: Move to Phobos std.traits
41  */
42 enum isAA(T) = __traits(isAssociativeArray, T); // TODO: check if in operator returns reference to value
43 
44 /// union of associative array (via keys)
45 @safe pure unittest
46 {
47     alias Map = string[int];
48 
49     Map x = [0 : "a", 1 : "b"];
50     Map y = [2 : "c"];
51 
52     Map c = [0 : "a", 1 : "b", 2 : "c"];
53 
54     // test associativity
55     assert(setUnionUpdate(x, y) == c);
56     assert(setUnionUpdate(y, x) == c);
57 }
58 
59 import std.traits : CommonType, isAssociativeArray;
60 import std.range.primitives;
61 import std.meta : anySatisfy, allSatisfy, staticMap;
62 import std.functional : binaryFun;
63 import std.range : SearchPolicy;
64 import nxt.range_ex : haveCommonElementType;
65 
66 private alias typeOfAAKeyValue(T) = typeof(T.init.byKeyValue);
67 private alias typeOfAAKey(T) = typeof(T.init.keys[0]);
68 private alias typeOfAAValue(T) = typeof(T.init.values[0]);
69 
70 /** Intersection of two or more associative arrays of type `Ts`.
71  *
72  * See_Also: https://forum.dlang.org/post/puwffthbqaktlqnourrs@forum.dlang.org
73  * TODO: Use switch + static foreach at https://forum.dlang.org/post/tdajgh$262i$1@digitalmars.com
74  */
75 auto setIntersectionAA(Ts...)(Ts inputs)
76 if (Ts.length >= 2 &&
77     allSatisfy!(isAssociativeArray, Ts) &&
78 	!is(CommonType!(staticMap!(typeOfAAKey, Ts)) == void) &&
79 	!is(CommonType!(staticMap!(typeOfAAValue, Ts)) == void))
80 {
81 	alias Ranges = staticMap!(typeOfAAKeyValue, Ts);
82 	alias KeyType = CommonType!(staticMap!(typeOfAAKey, Ts));
83 	alias ValueType = CommonType!(staticMap!(typeOfAAValue, Ts));
84 	struct Pair { KeyType key; ValueType value; }
85 	struct Result
86 	{
87 		alias E0 = typeof(Ranges[0].front);
88 		this(Ts inputs)
89 		{
90 			this._inputs = inputs;
91 			size_t minLength = inputs[0].length;
92 			static foreach (const i, input; inputs)
93 			{
94 				this._ranges[i] = input.byKeyValue;
95 				if (i != 0 &&
96 					input.length < minLength)
97 				{
98 					minLength = input.length;
99 					_shortestInputIndex = i;
100 				}
101 			}
102 			tryFindNextFront();
103 		}
104 		@property bool empty() /*TODO: const*/
105 		{
106 			final switch (_shortestInputIndex)
107 			{
108 				static foreach (i; 0 .. _ranges.length)
109 				{
110 				case i:
111 					return _ranges[i].empty;
112 				}
113 			}
114 		}
115 		@property /*TODO: inout*/ Pair front() /*TODO: inout*/
116 		{
117 			final switch (_shortestInputIndex)
118 			{
119 				static foreach (i; 0 .. _ranges.length)
120 				{
121 				case i:
122 					return typeof(return)(_ranges[i].front.key,
123 										  _ranges[i].front.value);
124 				}
125 			}
126 		}
127 		void popFront()
128 		{
129 			foreach (ref range; _ranges)
130 				range.popFront();
131 			tryFindNextFront();
132 		}
133 		private void tryFindNextFront()
134 		{
135 			while (!empty)
136 			{
137 				if (matchPopOtherFronts(front))
138 					break;
139 			sw:
140 				final switch (_shortestInputIndex)
141 				{
142 					static foreach (i; 0 .. _ranges.length)
143 					{
144 					case i:
145 						_ranges[i].popFront();
146 						break sw;
147 					}
148 				}
149 			}
150 		}
151 		private bool matchPopOtherFronts(scope const Pair baseFront)
152 		{
153 			typeof(return) result = true;
154 			static foreach (const i; 0 .. Ranges.length) // TODO: replace with `static foreach (range; ranges)` when possible
155 			{
156 				if (i != _shortestInputIndex)
157 				{
158 					if (_ranges[i].empty)
159 						return false;
160 					if (auto valuePtr = baseFront.key in inputs[i])
161 					{
162 						if (baseFront.value != *valuePtr)
163 						{
164 							_ranges[i].popFront();
165 							result = false;
166 						}
167 					}
168 					else
169 						result = false;
170 				}
171 			}
172 			return result;
173 		}
174 	private:
175 		Ts _inputs;
176 		Ranges _ranges;
177 		size_t _shortestInputIndex;
178 		invariant { assert(_shortestInputIndex < inputs.length); }
179 	}
180 	return Result(inputs);
181 }
182 
183 ///
184 @safe unittest
185 {
186 	alias T = int[string];
187 	T x2   = [       "1":1, "2":2,        "4":4];
188 	T x3   = ["0":0, "1":1, "2":2, "3":3, "4":4];
189 	T x4   = [       "1":1, "2":2, "3":3, "4":4, "5":5];
190 	T xh;
191 	auto y = setIntersectionAA(x2, x3, x4);
192 	assert(y._shortestInputIndex == 0);
193 	xh[y.front.key] = y.front.value;
194 	assert(!y.empty);
195 
196 	y.popFront();
197 	xh[y.front.key] = y.front.value;
198 	assert(!y.empty);
199 
200 	y.popFront();
201 	xh[y.front.key] = y.front.value;
202 	assert(!y.empty);
203 
204 	y.popFront();
205 	assert(y.empty);
206 
207 	assert(xh == x2);
208 }
209 
210 /** Intersection of two or more ranges of type `Rs`.
211  *
212  * See_Also: https://forum.dlang.org/post/puwffthbqaktlqnourrs@forum.dlang.org
213  */
214 private struct SetIntersectionFast(alias less = "a < b",
215                                    SearchPolicy preferredSearchPolicy = SearchPolicy.gallop,
216                                    Rs...)
217 if (Rs.length >= 2 &&
218     allSatisfy!(isInputRange, Rs) &&
219 	!anySatisfy!(isAssociativeArray, Rs) &&
220     haveCommonElementType!Rs)
221 {
222 private:
223     Rs _inputs;
224     alias comp = binaryFun!less;
225     alias ElementType = CommonType!(staticMap!(.ElementType, Rs));
226 
227     // Positions to the first elements that are all equal
228     void adjustPosition()
229     {
230         if (empty) return;
231 
232         auto compsLeft = Rs.length; // number of compares left
233         static if (Rs.length > 1) while (true)
234         {
235             foreach (i, ref r; _inputs)
236             {
237                 alias next = _inputs[(i + 1) % Rs.length]; // requires copying of range
238 
239                 // TODO: Use upperBound only when next.length / r.length > 12
240 
241                 import std.range.primitives : isRandomAccessRange;
242                 static if (allSatisfy!(isRandomAccessRange, typeof(next)))
243                 {
244                     import std.range : assumeSorted;
245 
246                     // TODO: remove need for this hack
247                     static if (less == "a < b")
248                         enum lessEq = "a <= b";
249                     else static if (less == "a > b")
250                         enum lessEq = "a >= b";
251 
252                     // TODO: can we merge thsse two lines two one single assignment from nextUpperBound to next
253                     auto nextUpperBound = next.assumeSorted!lessEq.upperBound!preferredSearchPolicy(r.front);
254                     next = next[$ - nextUpperBound.length .. $];
255 
256                     if (next.empty)
257                         return; // next became empty, so everything becomes empty
258                     else if (next.front != r.front)
259                         compsLeft = Rs.length; // we need to start counting comparing again starting with next.front
260                 }
261                 else
262                 {
263                     if (comp(next.front, r.front))
264                     {
265                         do
266                         {
267                             next.popFront();
268                             if (next.empty) return;
269                         }
270                         while (comp(next.front, r.front));
271                         compsLeft = Rs.length;
272                     }
273                 }
274                 if (--compsLeft == 0) return; // count down, and if we have made Rs.length iterations we are compsLeft finding a common front element
275             }
276         }
277     }
278 
279 public:
280     ///
281     this(Rs inputs)
282     {
283         import std.functional : forward;
284         this._inputs = inputs; // TODO: remove `forward` when compiler does it for us
285         // position to the first element
286         adjustPosition();
287     }
288 
289     ///
290     @property bool empty()
291     {
292         foreach (ref r; _inputs)
293             if (r.empty)
294 				return true;
295         return false;
296     }
297 
298     ///
299     void popFront() in(!empty)
300     {
301         static if (Rs.length > 1)
302 			foreach (i, ref r; _inputs)
303 			{
304 				alias next = _inputs[(i + 1) % Rs.length];
305 				assert(!comp(r.front, next.front));
306 			}
307 
308         foreach (ref r; _inputs)
309             r.popFront();
310         adjustPosition();
311     }
312 
313     ///
314     ElementType front() @property in(!empty) => _inputs[0].front;
315 
316     static if (allSatisfy!(isForwardRange, Rs))
317     {
318         ///
319         @property SetIntersectionFast save()
320         {
321             auto ret = this;
322             foreach (i, ref r; _inputs)
323                 ret._inputs[i] = r.save;
324             return ret;
325         }
326     }
327 }
328 
329 import core.internal.traits : Unqual;
330 
331 auto assumeMoveableSorted(alias pred = "a < b", R)(R r)
332 if (isInputRange!(Unqual!R))
333 {
334     import core.lifetime : move;
335     return MoveableSortedRange!(Unqual!R, pred)(move(r)); // TODO: remove `move` when compiler does it for us
336 }
337 
338 /** Get intersection of `ranges`.
339  *
340  * See_Also: https://forum.dlang.org/post/puwffthbqaktlqnourrs@forum.dlang.org
341  */
342 MoveableSortedRange!(SetIntersectionFast!(less, preferredSearchPolicy, Rs))
343 setIntersectionFast(alias less = "a < b",
344                     SearchPolicy preferredSearchPolicy = SearchPolicy.gallop,
345                     Rs...)(Rs ranges)
346 if (Rs.length >= 2 &&
347     allSatisfy!(isInputRange, Rs) &&
348     haveCommonElementType!Rs)
349 {
350     // TODO: Remove need for these switch cases if this can be fixed:
351     // http://forum.dlang.org/post/pknonazfniihvpicxbld@forum.dlang.org
352     import std.range : assumeSorted;
353     static if (Rs.length == 2)
354     {
355         import core.lifetime : move;
356         return assumeMoveableSorted(SetIntersectionFast!(less,
357                                                          preferredSearchPolicy,
358                                                          Rs)(move(ranges[0]), // TODO: remove `move` when compiler does it for us
359                                                              move(ranges[1]))); // TODO: remove `move` when compiler does it for us
360     }
361     else
362     {
363         import std.functional : forward;
364         return assumeMoveableSorted(SetIntersectionFast!(less,
365                                                          preferredSearchPolicy,
366                                                          Rs)(forward!ranges)); // TODO: remove `forward` when compiler does it for us
367     }
368 }
369 
370 @safe unittest
371 {
372     import std.algorithm.comparison : equal;
373     import std.algorithm.sorting : sort;
374     import std.algorithm.setops : setIntersection;
375     import nxt.random_ex : randInPlaceWithElementRange;
376     import nxt.container.dynamic_array : DynamicArray;
377     import nxt.algorithm_ex : collect;
378 
379     alias E = ulong;
380     alias A = DynamicArray!E;
381 
382     auto a0 = A();
383     auto a1 = A(1);
384 
385     enum less = "a < b";
386 
387     auto s0 = setIntersectionFast!(less)(a0[], a0[]);
388     assert(s0.equal(a0[]));
389 
390     auto s1 = setIntersectionFast!(less)(a1[], a1[]);
391     assert(s1.equal(a1[]));
392 
393     immutable smallTestLength = 1000;
394     immutable factor = 12; // this is the magical limit on my laptop when performance of `upperBound` beats standard implementation
395     immutable largeTestLength = factor*smallTestLength;
396     E elementLow = 0;
397     E elementHigh = 10_000_000;
398     auto x = A.withLength(smallTestLength);
399     auto y = A.withLength(largeTestLength);
400 
401     x[].randInPlaceWithElementRange(elementLow, elementHigh);
402     y[].randInPlaceWithElementRange(elementLow, elementHigh);
403 
404     sort(x[]);
405     sort(y[]);
406 
407     // associative
408     assert(equal(setIntersectionFast!(less)(x[], y[]),
409                  setIntersectionFast!(less)(y[], x[])));
410 
411     // same as current
412     assert(equal(setIntersection!(less)(x[], y[]),
413                  setIntersectionFast!(less)(x[], y[])));
414 
415     void testSetIntersection()
416     {
417         auto z = setIntersection!(less)(x[], y[]).collect!A;
418     }
419 
420     void testSetIntersectionNew()
421     {
422         auto z = setIntersectionFast!(less)(x[], y[]).collect!A;
423     }
424 
425     import std.datetime.stopwatch : benchmark;
426     import core.time : Duration;
427     immutable testCount = 10;
428     auto r = benchmark!(testSetIntersection,
429                         testSetIntersectionNew)(testCount);
430     version(show)
431     {
432         import std.stdio : writeln;
433         import std.conv : to;
434         writeln("old testSetIntersection: ", to!Duration(r[0]));
435         writeln("new testSetIntersection: ", to!Duration(r[1]));
436     }
437 }
438 
439 @safe pure nothrow unittest
440 {
441     import std.algorithm.comparison : equal;
442     enum less = "a < b";
443     auto si = setIntersectionFast!(less)([1, 2, 3],
444                                          [1, 2, 3]);
445     const sic = si.save();
446     assert(si.equal([1, 2, 3]));
447 }
448 
449 // TODO: remove this `MoveableSortedRange` and replace with Phobos' `SortedRange` in this buffer
450 struct MoveableSortedRange(Range, alias pred = "a < b")
451 if (isInputRange!Range)
452 {
453     import std.functional : binaryFun;
454 
455     private alias predFun = binaryFun!pred;
456     private bool geq(L, R)(L lhs, R rhs)
457     {
458         return !predFun(lhs, rhs);
459     }
460     private bool gt(L, R)(L lhs, R rhs)
461     {
462         return predFun(rhs, lhs);
463     }
464     private Range _input;
465 
466     // Undocummented because a clearer way to invoke is by calling
467     // assumeSorted.
468     this(Range input)
469     out
470     {
471         // moved out of the body as a workaround for Issue 12661
472         dbgVerifySorted();
473     }
474     do
475     {
476         import core.lifetime : move;
477         this._input = move(input); // TODO
478     }
479 
480     // Assertion only.
481     private void dbgVerifySorted()
482     {
483         if (!__ctfe)
484         debug
485         {
486             static if (isRandomAccessRange!Range && hasLength!Range)
487             {
488                 import core.bitop : bsr;
489                 import std.algorithm.sorting : isSorted;
490 
491                 // Check the sortedness of the input
492                 if (this._input.length < 2) return;
493 
494                 immutable size_t msb = bsr(this._input.length) + 1;
495                 assert(msb > 0 && msb <= this._input.length);
496                 immutable step = this._input.length / msb;
497                 auto st = stride(this._input, step);
498 
499                 assert(isSorted!pred(st), "Range is not sorted");
500             }
501         }
502     }
503 
504     /// Range primitives.
505     @property bool empty()             //const
506     {
507         return this._input.empty;
508     }
509 
510     /// Ditto
511     static if (isForwardRange!Range)
512     @property auto save()
513     {
514         // Avoid the constructor
515         typeof(this) result = this;
516         result._input = _input.save;
517         return result;
518     }
519 
520     /// Ditto
521     @property auto ref front()
522     {
523         return _input.front;
524     }
525 
526     /// Ditto
527     void popFront()
528     {
529         _input.popFront();
530     }
531 
532     /// Ditto
533     static if (isBidirectionalRange!Range)
534     {
535         @property auto ref back()
536         {
537             return _input.back;
538         }
539 
540         /// Ditto
541         void popBack()
542         {
543             _input.popBack();
544         }
545     }
546 
547     /// Ditto
548     static if (isRandomAccessRange!Range)
549         auto ref opIndex(size_t i)
550         {
551             return _input[i];
552         }
553 
554     /// Ditto
555     static if (hasSlicing!Range)
556         auto opSlice(size_t a, size_t b)
557         {
558             assert(
559                 a <= b,
560                 "Attempting to slice a SortedRange with a larger first argument than the second."
561             );
562             typeof(this) result = this;
563             result._input = _input[a .. b];// skip checking
564             return result;
565         }
566 
567     /// Ditto
568     static if (hasLength!Range)
569     {
570         @property size_t length()          //const
571         {
572             return _input.length;
573         }
574         alias opDollar = length;
575     }
576 
577 /**
578    Releases the controlled range and returns it.
579 */
580     auto release()
581     {
582         import core.lifetime : move;
583         return move(_input);
584     }
585 
586     // Assuming a predicate "test" that returns 0 for a left portion
587     // of the range and then 1 for the rest, returns the index at
588     // which the first 1 appears. Used internally by the search routines.
589     private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
590     if (sp == SearchPolicy.binarySearch && isRandomAccessRange!Range && hasLength!Range)
591     {
592         size_t first = 0, count = _input.length;
593         while (count > 0)
594         {
595             immutable step = count / 2, it = first + step;
596             if (!test(_input[it], v))
597             {
598                 first = it + 1;
599                 count -= step + 1;
600             }
601             else
602                 count = step;
603         }
604         return first;
605     }
606 
607     // Specialization for trot and gallop
608     private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
609     if ((sp == SearchPolicy.trot || sp == SearchPolicy.gallop)
610         && isRandomAccessRange!Range)
611     {
612         if (empty || test(front, v)) return 0;
613         immutable count = length;
614         if (count == 1) return 1;
615         size_t below = 0, above = 1, step = 2;
616         while (!test(_input[above], v))
617         {
618             // Still too small, update below and increase gait
619             below = above;
620             immutable next = above + step;
621             if (next >= count)
622             {
623                 // Overshot - the next step took us beyond the end. So
624                 // now adjust next and simply exit the loop to do the
625                 // binary search thingie.
626                 above = count;
627                 break;
628             }
629             // Still in business, increase step and continue
630             above = next;
631             static if (sp == SearchPolicy.trot)
632                 ++step;
633             else
634                 step <<= 1;
635         }
636         return below + this[below .. above].getTransitionIndex!(
637             SearchPolicy.binarySearch, test, V)(v);
638     }
639 
640     // Specialization for trotBackwards and gallopBackwards
641     private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
642     if ((sp == SearchPolicy.trotBackwards || sp == SearchPolicy.gallopBackwards)
643         && isRandomAccessRange!Range)
644     {
645         immutable count = length;
646         if (empty || !test(back, v)) return count;
647         if (count == 1) return 0;
648         size_t below = count - 2, above = count - 1, step = 2;
649         while (test(_input[below], v))
650         {
651             // Still too large, update above and increase gait
652             above = below;
653             if (below < step)
654             {
655                 // Overshot - the next step took us beyond the end. So
656                 // now adjust next and simply fall through to do the
657                 // binary search thingie.
658                 below = 0;
659                 break;
660             }
661             // Still in business, increase step and continue
662             below -= step;
663             static if (sp == SearchPolicy.trot)
664                 ++step;
665             else
666                 step <<= 1;
667         }
668         return below + this[below .. above].getTransitionIndex!(
669             SearchPolicy.binarySearch, test, V)(v);
670     }
671 
672 // lowerBound
673 /**
674    This function uses a search with policy $(D sp) to find the
675    largest left subrange on which $(D pred(x, value)) is `true` for
676    all $(D x) (e.g., if $(D pred) is "less than", returns the portion of
677    the range with elements strictly smaller than `value`). The search
678    schedule and its complexity are documented in
679    $(LREF SearchPolicy).  See also STL's
680    $(HTTP sgi.com/tech/stl/lower_bound.html, lower_bound).
681 */
682     auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value)
683     if (isTwoWayCompatible!(predFun, ElementType!Range, V)
684          && hasSlicing!Range)
685     {
686         return this[0 .. getTransitionIndex!(sp, geq)(value)];
687     }
688 
689 // upperBound
690 /**
691 This function searches with policy $(D sp) to find the largest right
692 subrange on which $(D pred(value, x)) is `true` for all $(D x)
693 (e.g., if $(D pred) is "less than", returns the portion of the range
694 with elements strictly greater than `value`). The search schedule
695 and its complexity are documented in $(LREF SearchPolicy).
696 
697 For ranges that do not offer random access, $(D SearchPolicy.linear)
698 is the only policy allowed (and it must be specified explicitly lest it exposes
699 user code to unexpected inefficiencies). For random-access searches, all
700 policies are allowed, and $(D SearchPolicy.binarySearch) is the default.
701 
702 See_Also: STL's $(HTTP sgi.com/tech/stl/lower_bound.html,upper_bound).
703 */
704     auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value)
705     if (isTwoWayCompatible!(predFun, ElementType!Range, V))
706     {
707         static assert(hasSlicing!Range || sp == SearchPolicy.linear,
708             "Specify SearchPolicy.linear explicitly for "
709             ~ typeof(this).stringof);
710         static if (sp == SearchPolicy.linear)
711         {
712             for (; !_input.empty && !predFun(value, _input.front);
713                  _input.popFront())
714             {
715             }
716             return this;
717         }
718         else
719         {
720             return this[getTransitionIndex!(sp, gt)(value) .. length];
721         }
722     }
723 
724 
725 // equalRange
726 /**
727    Returns the subrange containing all elements $(D e) for which both $(D
728    pred(e, value)) and $(D pred(value, e)) evaluate to $(D false) (e.g.,
729    if $(D pred) is "less than", returns the portion of the range with
730    elements equal to `value`). Uses a classic binary search with
731    interval halving until it finds a value that satisfies the condition,
732    then uses $(D SearchPolicy.gallopBackwards) to find the left boundary
733    and $(D SearchPolicy.gallop) to find the right boundary. These
734    policies are justified by the fact that the two boundaries are likely
735    to be near the first found value (i.e., equal ranges are relatively
736    small). Completes the entire search in $(BIGOH log(n)) time. See also
737    STL's $(HTTP sgi.com/tech/stl/equal_range.html, equal_range).
738 */
739     auto equalRange(V)(V value)
740     if (isTwoWayCompatible!(predFun, ElementType!Range, V)
741         && isRandomAccessRange!Range)
742     {
743         size_t first = 0, count = _input.length;
744         while (count > 0)
745         {
746             immutable step = count / 2;
747             auto it = first + step;
748             if (predFun(_input[it], value))
749             {
750                 // Less than value, bump left bound up
751                 first = it + 1;
752                 count -= step + 1;
753             }
754             else if (predFun(value, _input[it]))
755             {
756                 // Greater than value, chop count
757                 count = step;
758             }
759             else
760             {
761                 // Equal to value, do binary searches in the
762                 // leftover portions
763                 // Gallop towards the left end as it's likely nearby
764                 immutable left = first
765                     + this[first .. it]
766                     .lowerBound!(SearchPolicy.gallopBackwards)(value).length;
767                 first += count;
768                 // Gallop towards the right end as it's likely nearby
769                 immutable right = first
770                     - this[it + 1 .. first]
771                     .upperBound!(SearchPolicy.gallop)(value).length;
772                 return this[left .. right];
773             }
774         }
775         return this.init;
776     }
777 
778 // trisect
779 /**
780 Returns a tuple $(D r) such that $(D r[0]) is the same as the result
781 of $(D lowerBound(value)), $(D r[1]) is the same as the result of $(D
782 equalRange(value)), and $(D r[2]) is the same as the result of $(D
783 upperBound(value)). The call is faster than computing all three
784 separately. Uses a search schedule similar to $(D
785 equalRange). Completes the entire search in $(BIGOH log(n)) time.
786 */
787     auto trisect(V)(V value)
788     if (isTwoWayCompatible!(predFun, ElementType!Range, V)
789         && isRandomAccessRange!Range && hasLength!Range)
790     {
791         import std.typecons : tuple;
792         size_t first = 0, count = _input.length;
793         while (count > 0)
794         {
795             immutable step = count / 2;
796             auto it = first + step;
797             if (predFun(_input[it], value))
798             {
799                 // Less than value, bump left bound up
800                 first = it + 1;
801                 count -= step + 1;
802             }
803             else if (predFun(value, _input[it]))
804             {
805                 // Greater than value, chop count
806                 count = step;
807             }
808             else
809             {
810                 // Equal to value, do binary searches in the
811                 // leftover portions
812                 // Gallop towards the left end as it's likely nearby
813                 immutable left = first
814                     + this[first .. it]
815                     .lowerBound!(SearchPolicy.gallopBackwards)(value).length;
816                 first += count;
817                 // Gallop towards the right end as it's likely nearby
818                 immutable right = first
819                     - this[it + 1 .. first]
820                     .upperBound!(SearchPolicy.gallop)(value).length;
821                 return tuple(this[0 .. left], this[left .. right],
822                         this[right .. length]);
823             }
824         }
825         // No equal element was found
826         return tuple(this[0 .. first], this.init, this[first .. length]);
827     }
828 
829 // contains
830 /**
831 Returns `true` if and only if `value` can be found in $(D
832 range), which is assumed to be sorted. Performs $(BIGOH log(r.length))
833 evaluations of $(D pred). See also STL's $(HTTP
834 sgi.com/tech/stl/binary_search.html, binary_search).
835  */
836 
837     bool contains(V)(V value)
838     if (isRandomAccessRange!Range)
839     {
840         if (empty) return false;
841         immutable i = getTransitionIndex!(SearchPolicy.binarySearch, geq)(value);
842         if (i >= length) return false;
843         return !predFun(value, _input[i]);
844     }
845 
846 // groupBy
847 /**
848 Returns a range of subranges of elements that are equivalent according to the
849 sorting relation.
850  */
851     auto groupBy()()
852     {
853         import std.algorithm.iteration : chunkBy;
854         return _input.chunkBy!((a, b) => !predFun(a, b) && !predFun(b, a));
855     }
856 }