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 }