1 /** 2 A comprehensive sorting library for statistical functions. Each function 3 takes N arguments, which are arrays or array-like objects, sorts the first 4 and sorts the rest in lockstep. For merge and insertion sort, if the last 5 argument is a ulong*, increments the dereference of this ulong* by the bubble 6 sort distance between the first argument and the sorted version of the first 7 argument. This is useful for some statistical calculations. 8 9 All sorting functions have the precondition that all parallel input arrays 10 must have the same length. 11 12 Notes: 13 14 Comparison functions must be written such that compFun(x, x) == false. 15 For example, "a < b" is good, "a <= b" is not. 16 17 These functions are heavily optimized for sorting arrays of 18 ints and floats (by far the most common case when doing statistical 19 calculations). In these cases, they can be several times faster than the 20 equivalent functions in std.algorithm. Since sorting is extremely important 21 for non-parametric statistics, this results in important real-world 22 performance gains. However, it comes at a price in terms of generality: 23 24 1. They assume that what they are sorting is cheap to copy via normal 25 assignment. 26 27 2. They don't work at all with general ranges, only arrays and maybe 28 ranges very similar to arrays. 29 30 3. All tuning and micro-optimization is done with ints and floats, not 31 classes, large structs, strings, etc. 32 33 Examples: 34 --- 35 auto foo = [3, 1, 2, 4, 5].dup; 36 auto bar = [8, 6, 7, 5, 3].dup; 37 qsort(foo, bar); 38 assert(foo == [1, 2, 3, 4, 5]); 39 assert(bar == [6, 7, 8, 5, 3]); 40 auto baz = [1.0, 0, -1, -2, -3].dup; 41 mergeSort!("a > b")(bar, foo, baz); 42 assert(bar == [8, 7, 6, 5, 3]); 43 assert(foo == [3, 2, 1, 4, 5]); 44 assert(baz == [-1.0, 0, 1, -2, -3]); 45 --- 46 47 Author: David Simcha 48 */ 49 /* 50 * License: 51 * Boost Software License - Version 1.0 - August 17th, 2003 52 * 53 * Permission is hereby granted, free of charge, to any person or organization 54 * obtaining a copy of the software and accompanying documentation covered by 55 * this license (the "Software") to use, reproduce, display, distribute, 56 * execute, and transmit the Software, and to prepare derivative works of the 57 * Software, and to permit third-parties to whom the Software is furnished to 58 * do so, all subject to the following: 59 * 60 * The copyright notices in the Software and this entire statement, including 61 * the above license grant, this restriction and the following disclaimer, 62 * must be included in all copies of the Software, in whole or in part, and 63 * all derivative works of the Software, unless such copies or derivative 64 * works are solely in the form of machine-executable object code generated by 65 * a source language processor. 66 * 67 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 68 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 69 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 70 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 71 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 72 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 73 * DEALINGS IN THE SOFTWARE. 74 */ 75 76 module dstats.sort; 77 78 import std.traits, std.algorithm, std.math, std.functional, std.math, std.typecons, 79 std.typetuple, std.range, std.array, std.traits, std.ascii : whitespace; 80 81 import dstats.alloc; 82 83 version(unittest) { 84 import std.stdio, std.random; 85 } 86 87 class SortException : Exception { 88 this(string msg) { 89 super(msg); 90 } 91 } 92 93 /* CTFE function. Used in isSimpleComparison.*/ 94 /*private*/ string removeWhitespace(string input) pure nothrow { 95 string ret; 96 foreach(elem; input) { 97 bool shouldAppend = true; 98 foreach(whiteChar; whitespace) { 99 if(elem == whiteChar) { 100 shouldAppend = false; 101 break; 102 } 103 } 104 105 if(shouldAppend) { 106 ret ~= elem; 107 } 108 } 109 return ret; 110 } 111 112 /* Conservatively tests whether the comparison function is simple enough that 113 * we can get away with comparing floats as if they were ints. 114 */ 115 /*private*/ template isSimpleComparison(alias comp) { 116 static if(!isSomeString!(typeof(comp))) { 117 enum bool isSimpleComparison = false; 118 } else { 119 enum bool isSimpleComparison = 120 removeWhitespace(comp) == "a<b" || 121 removeWhitespace(comp) == "a>b"; 122 } 123 } 124 125 /*private*/ bool intIsNaN(I)(I i) { 126 static if(is(I == int) || is(I == uint)) { 127 // IEEE 754 single precision float has a 23-bit significand stored in the 128 // lowest order bits, followed by an 8-bit exponent. A NaN is when the 129 // exponent bits are all ones and the significand is nonzero. 130 enum uint significandMask = 0b111_1111_1111_1111_1111_1111UL; 131 enum uint exponentMask = 0b1111_1111UL << 23; 132 } else static if(is(I == long) || is(I == ulong)) { 133 // IEEE 754 double precision float has a 52-bit significand stored in the 134 // lowest order bits, followed by an 11-bit exponent. A NaN is when the 135 // exponent bits are all ones and the significand is nonzero. 136 enum ulong significandMask = 137 0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111UL; 138 enum ulong exponentMask = 0b111_1111_1111UL << 52; 139 } else { 140 static assert(0); 141 } 142 143 return ((i & exponentMask) == exponentMask) && ((i & significandMask) != 0); 144 } 145 146 unittest { 147 // Test on randomly generated integers punned to floats. We expect that 148 // about 1 in 256 will be NaNs. 149 foreach(i; 0..10_000) { 150 uint randInt = uniform(0U, uint.max); 151 assert(std.math.isNaN(*(cast(float*) &randInt)) == intIsNaN(randInt)); 152 } 153 154 // Test on randomly generated integers punned to doubles. We expect that 155 // about 1 in 2048 will be NaNs. 156 foreach(i; 0..1_000_000) { 157 ulong randInt = (cast(ulong) uniform(0U, uint.max) << 32) + uniform(0U, uint.max); 158 assert(std.math.isNaN(*(cast(double*) &randInt)) == intIsNaN(randInt)); 159 } 160 } 161 162 /* Check for NaN and do some bit twiddling so that a float or double can be 163 * compared as an integer. This results in approximately a 40% speedup 164 * compared to just sorting as floats. 165 */ 166 auto prepareForSorting(alias comp, T)(T arr) { 167 static if(isSimpleComparison!comp) { 168 static if(is(T == real[])) { 169 foreach(elem; arr) { 170 if(isNaN(elem)) { 171 throw new SortException("Can't sort NaNs."); 172 } 173 } 174 175 return arr; 176 } else static if(is(T == double[]) || is(T == float[])) { 177 static if(is(T == double[])) { 178 alias long Int; 179 enum signMask = 1UL << 63; 180 } else { 181 alias int Int; 182 enum signMask = 1U << 31; 183 } 184 185 Int[] intArr = cast(Int[]) arr; 186 foreach(i, ref elem; intArr) { 187 if(intIsNaN(elem)) { 188 // Roll back the bit twiddling in case someone catches the 189 // exception, so that they don't see corrupted values. 190 postProcess!comp(intArr[0..i]); 191 192 throw new SortException("Can't sort NaNs."); 193 } 194 195 if(elem & signMask) { 196 // Negative. 197 elem ^= signMask; 198 elem = ~elem; 199 } 200 } 201 202 return intArr; 203 } else { 204 return arr; 205 } 206 207 } else { 208 return arr; 209 } 210 } 211 212 /*private*/ void postProcess(alias comp, T)(T arr) 213 if(!isSimpleComparison!comp || (!is(T == double[]) && !is(T == float[]))) {} 214 215 /* Undo bit twiddling from prepareForSorting() to get back original 216 * floating point numbers. 217 */ 218 /*private*/ void postProcess(alias comp, F)(F arr) 219 if((is(F == double[]) || is(F == float[])) && isSimpleComparison!comp) { 220 static if(is(F == double[])) { 221 alias long Int; 222 enum mask = 1UL << 63; 223 } else { 224 alias int Int; 225 enum mask = 1U << 31; 226 } 227 228 Int[] useMe = cast(Int[]) arr; 229 foreach(ref elem; useMe) { 230 if(elem & mask) { 231 elem = ~elem; 232 elem ^= mask; 233 } 234 } 235 } 236 237 version(unittest) { 238 static void testFloating(alias fun, F)() { 239 F[] testL = new F[1_000]; 240 foreach(ref e; testL) { 241 e = uniform(-1_000_000, 1_000_000); 242 } 243 auto testL2 = testL.dup; 244 245 static if(__traits(isSame, fun, mergeSortTemp)) { 246 auto temp1 = testL.dup; 247 auto temp2 = testL.dup; 248 } 249 250 foreach(i; 0..200) { 251 randomShuffle(zip(testL, testL2)); 252 uint len = uniform(0, 1_000); 253 254 static if(__traits(isSame, fun, mergeSortTemp)) { 255 fun!"a > b"(testL[0..len], testL2[0..len], temp1[0..len], temp2[0..len]); 256 } else { 257 fun!("a > b")(testL[0..len], testL2[0..len]); 258 } 259 260 assert(isSorted!("a > b")(testL[0..len])); 261 assert(testL == testL2, fun.stringof ~ '\t' ~ F.stringof); 262 } 263 } 264 } 265 266 void rotateLeft(T)(T input) 267 if(isRandomAccessRange!(T)) { 268 if(input.length < 2) return; 269 ElementType!(T) temp = input[0]; 270 foreach(i; 1..input.length) { 271 input[i-1] = input[i]; 272 } 273 input[$-1] = temp; 274 } 275 276 void rotateRight(T)(T input) 277 if(isRandomAccessRange!(T)) { 278 if(input.length < 2) return; 279 ElementType!(T) temp = input[$-1]; 280 for(size_t i = input.length - 1; i > 0; i--) { 281 input[i] = input[i-1]; 282 } 283 input[0] = temp; 284 } 285 286 /* Returns the index, NOT the value, of the median of the first, middle, last 287 * elements of data.*/ 288 size_t medianOf3(alias compFun, T)(T[] data) { 289 alias binaryFun!(compFun) comp; 290 immutable size_t mid = data.length / 2; 291 immutable uint result = ((cast(uint) (comp(data[0], data[mid]))) << 2) | 292 ((cast(uint) (comp(data[0], data[$ - 1]))) << 1) | 293 (cast(uint) (comp(data[mid], data[$ - 1]))); 294 295 assert(result != 2 && result != 5 && result < 8); // Cases 2, 5 can't happen. 296 switch(result) { 297 case 1: // 001 298 case 6: // 110 299 return data.length - 1; 300 case 3: // 011 301 case 4: // 100 302 return 0; 303 case 0: // 000 304 case 7: // 111 305 return mid; 306 default: 307 assert(0); 308 } 309 assert(0); 310 } 311 312 unittest { 313 assert(medianOf3!("a < b")([1,2,3,4,5]) == 2); 314 assert(medianOf3!("a < b")([1,2,5,4,3]) == 4); 315 assert(medianOf3!("a < b")([3,2,1,4,5]) == 0); 316 assert(medianOf3!("a < b")([5,2,3,4,1]) == 2); 317 assert(medianOf3!("a < b")([5,2,1,4,3]) == 4); 318 assert(medianOf3!("a < b")([3,2,5,4,1]) == 0); 319 } 320 321 322 /**Quick sort. Unstable, O(N log N) time average, worst 323 * case, O(log N) space, small constant term in time complexity. 324 * 325 * In this implementation, the following steps are taken to avoid the 326 * O(N<sup>2</sup>) worst case of naive quick sorts: 327 * 328 * 1. At each recursion, the median of the first, middle and last elements of 329 * the array is used as the pivot. 330 * 331 * 2. To handle the case of few unique elements, the "Fit Pivot" technique 332 * previously decribed by Andrei Alexandrescu is used. This allows 333 * reasonable performance with few unique elements, with zero overhead 334 * in other cases. 335 * 336 * 3. After a much larger than expected amount of recursion has occured, 337 * this function transitions to a heap sort. This guarantees an O(N log N) 338 * worst case.*/ 339 T[0] qsort(alias compFun = "a < b", T...)(T data) 340 if(T.length != 0) 341 in { 342 assert(data.length > 0); 343 size_t len = data[0].length; 344 foreach(array; data[1..$]) { 345 assert(array.length == len); 346 } 347 } do { 348 if(data[0].length < 25) { 349 // Skip computing logarithm rather than waiting until qsortImpl to 350 // do this. 351 return insertionSort!compFun(data); 352 } 353 354 // Determines the transition point to a heap sort. 355 uint TTL = cast(uint) (log2(cast(real) data[0].length) * 2); 356 357 auto toSort = prepareForSorting!compFun(data[0]); 358 359 /* qsort() throws if an invalid comparison function is passed. Even in 360 * this case, the data should be post-processed so the bit twiddling 361 * hacks for floats can be undone. 362 */ 363 try { 364 qsortImpl!(compFun)(toSort, data[1..$], TTL); 365 } finally { 366 postProcess!compFun(data[0]); 367 } 368 369 return data[0]; 370 } 371 372 //TTL = time to live, before transitioning to heap sort. 373 void qsortImpl(alias compFun, T...)(T data, uint TTL) { 374 alias binaryFun!(compFun) comp; 375 if(data[0].length < 25) { 376 insertionSortImpl!(compFun)(data); 377 return; 378 } 379 if(TTL == 0) { 380 heapSortImpl!(compFun)(data); 381 return; 382 } 383 TTL--; 384 385 { 386 immutable size_t med3 = medianOf3!(comp)(data[0]); 387 foreach(array; data) { 388 auto temp = array[med3]; 389 array[med3] = array[$ - 1]; 390 array[$ - 1] = temp; 391 } 392 } 393 394 T less, greater; 395 size_t lessI = size_t.max, greaterI = data[0].length - 1; 396 397 auto pivot = data[0][$ - 1]; 398 if(comp(pivot, pivot)) { 399 throw new SortException 400 ("Comparison function must be such that compFun(x, x) == false."); 401 } 402 403 while(true) { 404 while(comp(data[0][++lessI], pivot)) {} 405 while(greaterI > 0 && comp(pivot, data[0][--greaterI])) {} 406 407 if(lessI < greaterI) { 408 foreach(array; data) { 409 auto temp = array[lessI]; 410 array[lessI] = array[greaterI]; 411 array[greaterI] = temp; 412 } 413 } else break; 414 } 415 416 foreach(ti, array; data) { 417 auto temp = array[$ - 1]; 418 array[$ - 1] = array[lessI]; 419 array[lessI] = temp; 420 less[ti] = array[0..min(lessI, greaterI + 1)]; 421 greater[ti] = array[lessI + 1..$]; 422 } 423 // Allow tail recursion optimization for larger block. This guarantees 424 // that, given a reasonable amount of stack space, no stack overflow will 425 // occur even in pathological cases. 426 if(greater[0].length > less[0].length) { 427 qsortImpl!(compFun)(less, TTL); 428 qsortImpl!(compFun)(greater, TTL); 429 return; 430 } else { 431 qsortImpl!(compFun)(greater, TTL); 432 qsortImpl!(compFun)(less, TTL); 433 } 434 } 435 436 unittest { 437 { // Test integer. 438 uint[] test = new uint[1_000]; 439 foreach(ref e; test) { 440 e = uniform(0, 100); 441 } 442 auto test2 = test.dup; 443 foreach(i; 0..1_000) { 444 randomShuffle(zip(test, test2)); 445 uint len = uniform(0, 1_000); 446 qsort(test[0..len], test2[0..len]); 447 assert(isSorted(test[0..len])); 448 assert(test == test2); 449 } 450 } 451 452 testFloating!(qsort, float)(); 453 testFloating!(qsort, double)(); 454 testFloating!(qsort, real)(); 455 456 auto nanArr = [double.nan, 1.0]; 457 try { 458 qsort(nanArr); 459 assert(0); 460 } catch(SortException) {} 461 } 462 463 /* Keeps track of what array merge sort data is in. This is a speed hack to 464 * copy back and forth less.*/ 465 /*private*/ enum { 466 DATA, 467 TEMP 468 } 469 470 /**Merge sort. O(N log N) time, O(N) space, small constant. Stable sort. 471 * If last argument is a ulong* instead of an array-like type, 472 * the dereference of the ulong* will be incremented by the bubble sort 473 * distance between the input array and the sorted version. This is useful 474 * in some statistics functions such as Kendall's tau.*/ 475 T[0] mergeSort(alias compFun = "a < b", T...)(T data) 476 if(T.length != 0) 477 in { 478 assert(data.length > 0); 479 size_t len = data[0].length; 480 foreach(array; data[1..$]) { 481 static if(!is(typeof(array) == ulong*)) 482 assert(array.length == len); 483 } 484 } do { 485 if(data[0].length < 65) { //Avoid mem allocation. 486 return insertionSortImpl!(compFun)(data); 487 } 488 static if(is(T[$ - 1] == ulong*)) { 489 enum dl = data.length - 1; 490 alias data[$ - 1] swapCount; 491 } else { 492 enum dl = data.length; 493 alias TypeTuple!() swapCount; // Place holder. 494 } 495 496 auto keyArr = prepareForSorting!compFun(data[0]); 497 auto toSort = TypeTuple!(keyArr, data[1..dl]); 498 499 typeof(toSort) temp; 500 auto alloc = newRegionAllocator(); 501 foreach(i, array; temp) { 502 temp[i] = alloc.uninitializedArray!(typeof(temp[i][0])[])(data[i].length); 503 } 504 505 uint res = mergeSortImpl!(compFun)(toSort, temp, swapCount); 506 if(res == TEMP) { 507 foreach(ti, array; temp) { 508 toSort[ti][0..$] = temp[ti][0..$]; 509 } 510 } 511 512 postProcess!compFun(data[0]); 513 return data[0]; 514 } 515 516 unittest { 517 uint[] test = new uint[1_000], stability = new uint[1_000]; 518 uint[] temp1 = new uint[1_000], temp2 = new uint[1_000]; 519 foreach(ref e; test) { 520 e = uniform(0, 100); //Lots of ties. 521 } 522 foreach(i; 0..100) { 523 ulong mergeCount = 0, bubbleCount = 0; 524 foreach(j, ref e; stability) { 525 e = cast(uint) j; 526 } 527 randomShuffle(test); 528 uint len = uniform(0, 1_000); 529 // Testing bubble sort distance against bubble sort, 530 // since bubble sort distance computed by bubble sort 531 // is straightforward, unlikely to contain any subtle bugs. 532 bubbleSort(test[0..len].dup, &bubbleCount); 533 if(i & 1) // Test both temp and non-temp branches. 534 mergeSort(test[0..len], stability[0..len], &mergeCount); 535 else 536 mergeSortTemp(test[0..len], stability[0..len], temp1[0..len], 537 temp2[0..len], &mergeCount); 538 assert(bubbleCount == mergeCount); 539 assert(isSorted(test[0..len])); 540 foreach(j; 1..len) { 541 if(test[j - 1] == test[j]) { 542 assert(stability[j - 1] < stability[j]); 543 } 544 } 545 } 546 // Test without swapCounts. 547 foreach(i; 0..1000) { 548 foreach(j, ref e; stability) { 549 e = cast(uint) j; 550 } 551 randomShuffle(test); 552 uint len = uniform(0, 1_000); 553 if(i & 1) // Test both temp and non-temp branches. 554 mergeSort(test[0..len], stability[0..len]); 555 else 556 mergeSortTemp(test[0..len], stability[0..len], temp1[0..len], 557 temp2[0..len]); 558 assert(isSorted(test[0..len])); 559 foreach(j; 1..len) { 560 if(test[j - 1] == test[j]) { 561 assert(stability[j - 1] < stability[j]); 562 } 563 } 564 } 565 566 testFloating!(mergeSort, float)(); 567 testFloating!(mergeSort, double)(); 568 testFloating!(mergeSort, real)(); 569 570 testFloating!(mergeSortTemp, float)(); 571 testFloating!(mergeSortTemp, double)(); 572 testFloating!(mergeSortTemp, real)(); 573 } 574 575 /**Merge sort, allowing caller to provide a temp variable. This allows 576 * recycling instead of repeated allocations. If D is data, T is temp, 577 * and U is a ulong* for calculating bubble sort distance, this can be called 578 * as mergeSortTemp(D, D, D, T, T, T, U) or mergeSortTemp(D, D, D, T, T, T) 579 * where each D has a T of corresponding type. 580 * 581 * Examples: 582 * --- 583 * int[] foo = [3, 1, 2, 4, 5].dup; 584 * int[] temp = new uint[5]; 585 * mergeSortTemp!("a < b")(foo, temp); 586 * assert(foo == [1, 2, 3, 4, 5]); // The contents of temp will be undefined. 587 * foo = [3, 1, 2, 4, 5].dup; 588 * real bar = [3.14L, 15.9, 26.5, 35.8, 97.9]; 589 * real temp2 = new real[5]; 590 * mergeSortTemp(foo, bar, temp, temp2); 591 * assert(foo == [1, 2, 3, 4, 5]); 592 * assert(bar == [15.9L, 26.5, 3.14, 35.8, 97.9]); 593 * // The contents of both temp and temp2 will be undefined. 594 * --- 595 */ 596 T[0] mergeSortTemp(alias compFun = "a < b", T...)(T data) 597 if(T.length != 0) 598 in { 599 assert(data.length > 0); 600 size_t len = data[0].length; 601 foreach(array; data[1..$]) { 602 static if(!is(typeof(array) == ulong*)) 603 assert(array.length == len); 604 } 605 } do { 606 static if(is(T[$ - 1] == ulong*)) { 607 enum dl = data.length - 1; 608 } else { 609 enum dl = data.length; 610 } 611 612 auto keyArr = prepareForSorting!compFun(data[0]); 613 auto keyTemp = cast(typeof(keyArr)) data[dl / 2]; 614 auto toSort = TypeTuple!( 615 keyArr, 616 data[1..dl / 2], 617 keyTemp, 618 data[dl / 2 + 1..$] 619 ); 620 621 uint res = mergeSortImpl!(compFun)(toSort); 622 623 if(res == TEMP) { 624 foreach(ti, array; toSort[0..$ / 2]) { 625 toSort[ti][0..$] = toSort[ti + dl / 2][0..$]; 626 } 627 } 628 629 postProcess!compFun(data[0]); 630 return data[0]; 631 } 632 633 /*private*/ uint mergeSortImpl(alias compFun = "a < b", T...)(T dataIn) { 634 static if(is(T[$ - 1] == ulong*)) { 635 alias dataIn[$ - 1] swapCount; 636 alias dataIn[0..dataIn.length / 2] data; 637 alias dataIn[dataIn.length / 2..$ - 1] temp; 638 } else { // Make empty dummy tuple. 639 alias TypeTuple!() swapCount; 640 alias dataIn[0..dataIn.length / 2] data; 641 alias dataIn[dataIn.length / 2..$] temp; 642 } 643 644 if(data[0].length < 50) { 645 insertionSortImpl!(compFun)(data, swapCount); 646 return DATA; 647 } 648 size_t half = data[0].length / 2; 649 typeof(data) left, right, tempLeft, tempRight; 650 foreach(ti, array; data) { 651 left[ti] = array[0..half]; 652 right[ti] = array[half..$]; 653 tempLeft[ti] = temp[ti][0..half]; 654 tempRight[ti] = temp[ti][half..$]; 655 } 656 657 /* Implementation note: The lloc, rloc stuff is a hack to avoid constantly 658 * copying data back and forth between the data and temp arrays. 659 * Instad of copying every time, I keep track of which array the last merge 660 * went into, and only copy at the end or if the two sides ended up in 661 * different arrays.*/ 662 uint lloc = mergeSortImpl!(compFun)(left, tempLeft, swapCount); 663 uint rloc = mergeSortImpl!(compFun)(right, tempRight, swapCount); 664 if(lloc == DATA && rloc == TEMP) { 665 foreach(ti, array; tempLeft) { 666 array[] = left[ti][]; 667 } 668 lloc = TEMP; 669 } else if(lloc == TEMP && rloc == DATA) { 670 foreach(ti, array; tempRight) { 671 array[] = right[ti][]; 672 } 673 } 674 if(lloc == DATA) { 675 merge!(compFun)(left, right, temp, swapCount); 676 return TEMP; 677 } else { 678 merge!(compFun)(tempLeft, tempRight, data, swapCount); 679 return DATA; 680 } 681 } 682 683 /*private*/ void merge(alias compFun, T...)(T data) { 684 alias binaryFun!(compFun) comp; 685 686 static if(is(T[$ - 1] == ulong*)) { 687 enum dl = data.length - 1; //Length after removing swapCount; 688 alias data[$ - 1] swapCount; 689 } else { 690 enum dl = data.length; 691 } 692 693 static assert(dl % 3 == 0); 694 alias data[0..dl / 3] left; 695 alias data[dl / 3..dl * 2 / 3] right; 696 alias data[dl * 2 / 3..dl] result; 697 static assert(left.length == right.length && right.length == result.length); 698 size_t i = 0, l = 0, r = 0; 699 while(l < left[0].length && r < right[0].length) { 700 if(comp(right[0][r], left[0][l])) { 701 702 static if(is(T[$ - 1] == ulong*)) { 703 *swapCount += left[0].length - l; 704 } 705 706 foreach(ti, array; result) { 707 result[ti][i] = right[ti][r]; 708 } 709 r++; 710 } else { 711 foreach(ti, array; result) { 712 result[ti][i] = left[ti][l]; 713 } 714 l++; 715 } 716 i++; 717 } 718 if(right[0].length > r) { 719 foreach(ti, array; result) { 720 result[ti][i..$] = right[ti][r..$]; 721 } 722 } else { 723 foreach(ti, array; result) { 724 result[ti][i..$] = left[ti][l..$]; 725 } 726 } 727 } 728 729 /**In-place merge sort, based on C++ STL's stable_sort(). O(N log<sup>2</sup> N) 730 * time complexity, O(1) space complexity, stable. Much slower than plain 731 * old mergeSort(), so only use it if you really need the O(1) space.*/ 732 T[0] mergeSortInPlace(alias compFun = "a < b", T...)(T data) 733 if(T.length != 0) 734 in { 735 assert(data.length > 0); 736 size_t len = data[0].length; 737 foreach(array; data[1..$]) { 738 assert(array.length == len); 739 } 740 } do { 741 auto toSort = prepareForSorting!compFun(data[0]); 742 mergeSortInPlaceImpl!compFun(toSort, data[1..$]); 743 postProcess!compFun(data[0]); 744 return data[0]; 745 } 746 747 /*private*/ T[0] mergeSortInPlaceImpl(alias compFun, T...)(T data) { 748 if (data[0].length <= 100) 749 return insertionSortImpl!(compFun)(data); 750 751 T left, right; 752 foreach(ti, array; data) { 753 left[ti] = array[0..$ / 2]; 754 right[ti] = array[$ / 2..$]; 755 } 756 757 mergeSortInPlace!(compFun, T)(right); 758 mergeSortInPlace!(compFun, T)(left); 759 mergeInPlace!(compFun)(data, data[0].length / 2); 760 return data[0]; 761 } 762 763 unittest { 764 uint[] test = new uint[1_000], stability = new uint[1_000]; 765 foreach(ref e; test) { 766 e = uniform(0, 100); //Lots of ties. 767 } 768 uint[] test2 = test.dup; 769 foreach(i; 0..1000) { 770 foreach(j, ref e; stability) { 771 e = cast(uint) j; 772 } 773 randomShuffle(zip(test, test2)); 774 uint len = uniform(0, 1_000); 775 mergeSortInPlace(test[0..len], test2[0..len], stability[0..len]); 776 assert(isSorted(test[0..len])); 777 assert(test == test2); 778 foreach(j; 1..len) { 779 if(test[j - 1] == test[j]) { 780 assert(stability[j - 1] < stability[j]); 781 } 782 } 783 } 784 785 testFloating!(mergeSortInPlace, float)(); 786 testFloating!(mergeSortInPlace, double)(); 787 testFloating!(mergeSortInPlace, real)(); 788 } 789 790 // Loosely based on C++ STL's __merge_without_buffer(). 791 /*private*/ void mergeInPlace(alias compFun = "a < b", T...)(T data, size_t middle) { 792 alias binaryFun!(compFun) comp; 793 794 static size_t largestLess(T)(T[] data, T value) { 795 return assumeSorted!(comp)(data).lowerBound(value).length; 796 } 797 798 static size_t smallestGr(T)(T[] data, T value) { 799 return data.length - 800 assumeSorted!(comp)(data).upperBound(value).length; 801 } 802 803 804 if (data[0].length < 2 || middle == 0 || middle == data[0].length) { 805 return; 806 } 807 808 if (data[0].length == 2) { 809 if(comp(data[0][1], data[0][0])) { 810 foreach(array; data) { 811 auto temp = array[0]; 812 array[0] = array[1]; 813 array[1] = temp; 814 } 815 } 816 return; 817 } 818 819 size_t half1, half2, firstCut, secondCut; 820 821 if (middle > data[0].length - middle) { 822 half1 = middle / 2; 823 auto pivot = data[0][half1]; 824 half2 = largestLess(data[0][middle..$], pivot); 825 } else { 826 half2 = (data[0].length - middle) / 2; 827 auto pivot = data[0][half2 + middle]; 828 half1 = smallestGr(data[0][0..middle], pivot); 829 } 830 831 foreach(array; data) { 832 bringToFront(array[half1..middle], array[middle..middle + half2]); 833 } 834 size_t newMiddle = half1 + half2; 835 836 T left, right; 837 foreach(ti, array; data) { 838 left[ti] = array[0..newMiddle]; 839 right[ti] = array[newMiddle..$]; 840 } 841 842 mergeInPlace!(compFun, T)(left, half1); 843 mergeInPlace!(compFun, T)(right, half2 + middle - newMiddle); 844 } 845 846 847 /**Heap sort. Unstable, O(N log N) time average and worst case, O(1) space, 848 * large constant term in time complexity.*/ 849 T[0] heapSort(alias compFun = "a < b", T...)(T data) 850 if(T.length != 0) 851 in { 852 assert(data.length > 0); 853 size_t len = data[0].length; 854 foreach(array; data[1..$]) { 855 assert(array.length == len); 856 } 857 } do { 858 auto toSort = prepareForSorting!compFun(data[0]); 859 heapSortImpl!compFun(toSort, data[1..$]); 860 postProcess!compFun(data[0]); 861 return data[0]; 862 } 863 864 /*private*/ T[0] heapSortImpl(alias compFun, T...)(T input) { 865 // Heap sort has such a huge constant that insertion sort's faster for N < 866 // 100 (for reals; even larger for smaller types). 867 if(input[0].length <= 100) { 868 return insertionSortImpl!(compFun)(input); 869 } 870 871 alias binaryFun!(compFun) comp; 872 if(input[0].length < 2) return input[0]; 873 makeMultiHeap!(compFun)(input); 874 for(size_t end = input[0].length - 1; end > 0; end--) { 875 foreach(ti, ia; input) { 876 auto temp = ia[end]; 877 ia[end] = ia[0]; 878 ia[0] = temp; 879 } 880 multiSiftDown!(compFun)(input, 0, end); 881 } 882 return input[0]; 883 } 884 885 unittest { 886 uint[] test = new uint[1_000]; 887 foreach(ref e; test) { 888 e = uniform(0, 100_000); 889 } 890 auto test2 = test.dup; 891 foreach(i; 0..1_000) { 892 randomShuffle(zip(test, test2)); 893 uint len = uniform(0, 1_000); 894 heapSort(test[0..len], test2[0..len]); 895 assert(isSorted(test[0..len])); 896 assert(test == test2); 897 } 898 899 testFloating!(heapSort, float)(); 900 testFloating!(heapSort, double)(); 901 testFloating!(heapSort, real)(); 902 } 903 904 void makeMultiHeap(alias compFun = "a < b", T...)(T input) { 905 if(input[0].length < 2) 906 return; 907 alias binaryFun!(compFun) comp; 908 for(sizediff_t start = (input[0].length - 1) / 2; start >= 0; start--) { 909 multiSiftDown!(compFun)(input, start, input[0].length); 910 } 911 } 912 913 void multiSiftDown(alias compFun = "a < b", T...) 914 (T input, size_t root, size_t end) { 915 alias binaryFun!(compFun) comp; 916 alias input[0] a; 917 while(root * 2 + 1 < end) { 918 size_t child = root * 2 + 1; 919 if(child + 1 < end && comp(a[child], a[child + 1])) { 920 child++; 921 } 922 if(comp(a[root], a[child])) { 923 foreach(ia; input) { 924 auto temp = ia[root]; 925 ia[root] = ia[child]; 926 ia[child] = temp; 927 } 928 root = child; 929 } 930 else return; 931 } 932 } 933 934 /**Insertion sort. O(N<sup>2</sup>) time worst, average case, O(1) space, VERY 935 * small constant, which is why it's useful for sorting small subarrays in 936 * divide and conquer algorithms. If last argument is a ulong*, increments 937 * the dereference of this argument by the bubble sort distance between the 938 * input array and the sorted version of the input.*/ 939 T[0] insertionSort(alias compFun = "a < b", T...)(T data) 940 in { 941 assert(data.length > 0); 942 size_t len = data[0].length; 943 foreach(array; data[1..$]) { 944 static if(!is(typeof(array) == ulong*)) 945 assert(array.length == len); 946 } 947 } do { 948 auto toSort = prepareForSorting!compFun(data[0]); 949 insertionSortImpl!compFun(toSort, data[1..$]); 950 postProcess!compFun(data[0]); 951 return data[0]; 952 } 953 954 private template IndexType(T) { 955 alias typeof(T.init[0]) IndexType; 956 } 957 958 /*private*/ T[0] insertionSortImpl(alias compFun, T...)(T data) { 959 alias binaryFun!(compFun) comp; 960 static if(is(T[$ - 1] == ulong*)) { 961 enum dl = data.length - 1; 962 alias data[$ - 1] swapCount; 963 } else { 964 enum dl = data.length; 965 } 966 967 alias data[0] keyArray; 968 if(keyArray.length < 2) { 969 return keyArray; 970 } 971 972 // Yes, I measured this, caching this value is actually faster on DMD. 973 immutable maxJ = keyArray.length - 1; 974 for(size_t i = keyArray.length - 2; i != size_t.max; --i) { 975 size_t j = i; 976 977 Tuple!(staticMap!(IndexType, typeof(data[0..dl]))) temp = void; 978 foreach(ti, Type; typeof(data[0..dl])) { 979 static if(hasElaborateAssign!Type) { 980 emplace(&(temp.field[ti]), data[ti][i]); 981 } else { 982 temp.field[ti] = data[ti][i]; 983 } 984 } 985 986 for(; j < maxJ && comp(keyArray[j + 1], temp.field[0]); ++j) { 987 // It's faster to do all copying here than to call rotateLeft() 988 // later, probably due to better ILP. 989 foreach(array; data[0..dl]) { 990 array[j] = array[j + 1]; 991 } 992 } 993 994 foreach(ti, Unused; typeof(temp.field)) { 995 data[ti][j] = temp.field[ti]; 996 } 997 998 static if(is(typeof(swapCount))) { 999 *swapCount += (j - i); //Increment swapCount variable. 1000 } 1001 } 1002 1003 return keyArray; 1004 } 1005 1006 unittest { 1007 uint[] test = new uint[100], stability = new uint[100]; 1008 foreach(ref e; test) { 1009 e = uniform(0, 100); //Lots of ties. 1010 } 1011 foreach(i; 0..1_000) { 1012 ulong insertCount = 0, bubbleCount = 0; 1013 foreach(j, ref e; stability) { 1014 e = cast(uint) j; 1015 } 1016 randomShuffle(test); 1017 uint len = uniform(0, 100); 1018 // Testing bubble sort distance against bubble sort, 1019 // since bubble sort distance computed by bubble sort 1020 // is straightforward, unlikely to contain any subtle bugs. 1021 bubbleSort(test[0..len].dup, &bubbleCount); 1022 insertionSort(test[0..len], stability[0..len], &insertCount); 1023 assert(bubbleCount == insertCount); 1024 assert(isSorted(test[0..len])); 1025 foreach(j; 1..len) { 1026 if(test[j - 1] == test[j]) { 1027 assert(stability[j - 1] < stability[j]); 1028 } 1029 } 1030 } 1031 } 1032 1033 // Kept around only because it's easy to implement, and therefore good for 1034 // testing more complex sort functions against. Especially useful for bubble 1035 // sort distance, since it's straightforward with a bubble sort, and not with 1036 // a merge sort or insertion sort. 1037 version(unittest) { 1038 T[0] bubbleSort(alias compFun = "a < b", T...)(T data) { 1039 alias binaryFun!(compFun) comp; 1040 static if(is(T[$ - 1] == ulong*)) 1041 enum dl = data.length - 1; 1042 else enum dl = data.length; 1043 if(data[0].length < 2) 1044 return data[0]; 1045 bool swapExecuted; 1046 foreach(i; 0..data[0].length) { 1047 swapExecuted = false; 1048 foreach(j; 1..data[0].length) { 1049 if(comp(data[0][j], data[0][j - 1])) { 1050 swapExecuted = true; 1051 static if(is(T[$ - 1] == ulong*)) 1052 (*(data[$-1]))++; 1053 foreach(array; data[0..dl]) 1054 swap(array[j-1], array[j]); 1055 } 1056 } 1057 if(!swapExecuted) return data[0]; 1058 } 1059 return data[0]; 1060 } 1061 } 1062 1063 unittest { 1064 //Sanity check for bubble sort distance. 1065 uint[] test = [4, 5, 3, 2, 1]; 1066 ulong dist = 0; 1067 bubbleSort(test, &dist); 1068 assert(dist == 9); 1069 dist = 0; 1070 test = [6, 1, 2, 4, 5, 3]; 1071 bubbleSort(test, &dist); 1072 assert(dist == 7); 1073 } 1074 1075 /**Returns the kth largest/smallest element (depending on compFun, 0-indexed) 1076 * in the input array in O(N) time. Allocates memory, does not modify input 1077 * array.*/ 1078 T quickSelect(alias compFun = "a < b", T)(T[] data, sizediff_t k) { 1079 auto alloc = newRegionAllocator(); 1080 auto dataDup = alloc.array(data); 1081 return partitionK!(compFun)(dataDup, k); 1082 } 1083 1084 /**Partitions the input data according to compFun, such that position k contains 1085 * the kth largest/smallest element according to compFun. For all elements e 1086 * with indices < k, !compFun(data[k], e) is guaranteed to be true. For all 1087 * elements e with indices > k, !compFun(e, data[k]) is guaranteed to be true. 1088 * For example, if compFun is "a < b", all elements with indices < k will be 1089 * <= data[k], and all elements with indices larger than k will be >= k. 1090 * Reorders any additional input arrays in lockstep. 1091 * 1092 * Examples: 1093 * --- 1094 * auto foo = [3, 1, 5, 4, 2].dup; 1095 * auto secondSmallest = partitionK(foo, 1); 1096 * assert(secondSmallest == 2); 1097 * foreach(elem; foo[0..1]) { 1098 * assert(elem <= foo[1]); 1099 * } 1100 * foreach(elem; foo[2..$]) { 1101 * assert(elem >= foo[1]); 1102 * } 1103 * --- 1104 * 1105 * Returns: The kth element of the array. 1106 */ 1107 ElementType!(T[0]) partitionK(alias compFun = "a < b", T...)(T data, ptrdiff_t k) 1108 in { 1109 assert(data.length > 0); 1110 size_t len = data[0].length; 1111 foreach(array; data[1..$]) { 1112 assert(array.length == len); 1113 } 1114 } do { 1115 // Don't use the float-to-int trick because it's actually slower here 1116 // because the main part of the algorithm is O(N), not O(N log N). 1117 return partitionKImpl!compFun(data, k); 1118 } 1119 1120 /*private*/ ElementType!(T[0]) partitionKImpl(alias compFun, T...)(T data, ptrdiff_t k) { 1121 alias binaryFun!(compFun) comp; 1122 1123 { 1124 immutable size_t med3 = medianOf3!(comp)(data[0]); 1125 foreach(array; data) { 1126 auto temp = array[med3]; 1127 array[med3] = array[$ - 1]; 1128 array[$ - 1] = temp; 1129 } 1130 } 1131 1132 ptrdiff_t lessI = -1, greaterI = data[0].length - 1; 1133 auto pivot = data[0][$ - 1]; 1134 while(true) { 1135 while(comp(data[0][++lessI], pivot)) {} 1136 while(greaterI > 0 && comp(pivot, data[0][--greaterI])) {} 1137 1138 if(lessI < greaterI) { 1139 foreach(array; data) { 1140 auto temp = array[lessI]; 1141 array[lessI] = array[greaterI]; 1142 array[greaterI] = temp; 1143 } 1144 } else break; 1145 } 1146 foreach(array; data) { 1147 auto temp = array[lessI]; 1148 array[lessI] = array[$ - 1]; 1149 array[$ - 1] = temp; 1150 } 1151 1152 if((greaterI < k && lessI >= k) || lessI == k) { 1153 return data[0][k]; 1154 } else if(lessI < k) { 1155 foreach(ti, array; data) { 1156 data[ti] = array[lessI + 1..$]; 1157 } 1158 return partitionK!(compFun, T)(data, k - lessI - 1); 1159 } else { 1160 foreach(ti, array; data) { 1161 data[ti] = array[0..min(greaterI + 1, lessI)]; 1162 } 1163 return partitionK!(compFun, T)(data, k); 1164 } 1165 } 1166 1167 template ArrayElemType(T : T[]) { 1168 alias T ArrayElemType; 1169 } 1170 1171 unittest { 1172 enum n = 1000; 1173 uint[] test = new uint[n]; 1174 uint[] test2 = new uint[n]; 1175 uint[] lockstep = new uint[n]; 1176 foreach(ref e; test) { 1177 e = uniform(0, 1000); 1178 } 1179 foreach(i; 0..1_000) { 1180 test2[] = test[]; 1181 lockstep[] = test[]; 1182 uint len = uniform(0, n - 1) + 1; 1183 qsort!("a > b")(test2[0..len]); 1184 int k = uniform(0, len); 1185 auto qsRes = partitionK!("a > b")(test[0..len], lockstep[0..len], k); 1186 assert(qsRes == test2[k]); 1187 foreach(elem; test[0..k]) { 1188 assert(elem >= test[k]); 1189 } 1190 foreach(elem; test[k + 1..len]) { 1191 assert(elem <= test[k]); 1192 } 1193 assert(test == lockstep); 1194 } 1195 } 1196 1197 /**Given a set of data points entered through the put function, this output range 1198 * maintains the invariant that the top N according to compFun will be 1199 * contained in the data structure. Uses a heap internally, O(log N) insertion 1200 * time. Good for finding the largest/smallest N elements of a very large 1201 * dataset that cannot be sorted quickly in its entirety, and may not even fit 1202 * in memory. If less than N datapoints have been entered, all are contained in 1203 * the structure. 1204 * 1205 * Examples: 1206 * --- 1207 * Random gen; 1208 * gen.seed(unpredictableSeed); 1209 * uint[] nums = seq(0U, 100U); 1210 * auto less = TopN!(uint, "a < b")(10); 1211 * auto more = TopN!(uint, "a > b")(10); 1212 * randomShuffle(nums, gen); 1213 * foreach(n; nums) { 1214 * less.put(n); 1215 * more.put(n); 1216 * } 1217 * assert(less.getSorted == [0U, 1,2,3,4,5,6,7,8,9]); 1218 * assert(more.getSorted == [99U, 98, 97, 96, 95, 94, 93, 92, 91, 90]); 1219 * --- 1220 */ 1221 struct TopN(T, alias compFun = "a > b") { 1222 private: 1223 alias binaryFun!(compFun) comp; 1224 uint n; 1225 uint nAdded; 1226 1227 T[] nodes; 1228 public: 1229 /** The variable ntop controls how many elements are retained.*/ 1230 this(uint ntop) { 1231 n = ntop; 1232 nodes = new T[n]; 1233 } 1234 1235 /** Insert an element into the topN struct.*/ 1236 void put(T elem) { 1237 if(nAdded < n) { 1238 nodes[nAdded] = elem; 1239 if(nAdded == n - 1) { 1240 makeMultiHeap!(comp)(nodes); 1241 } 1242 nAdded++; 1243 } else if(nAdded >= n) { 1244 if(comp(elem, nodes[0])) { 1245 nodes[0] = elem; 1246 multiSiftDown!(comp)(nodes, 0, nodes.length); 1247 } 1248 } 1249 } 1250 1251 /**Get the elements currently in the struct. Returns a reference to 1252 * internal state, elements will be in an arbitrary order. Cheap.*/ 1253 T[] getElements() { 1254 return nodes[0..min(n, nAdded)]; 1255 } 1256 1257 /**Returns the elements sorted by compFun. The array returned is a 1258 * duplicate of the input array. Not cheap.*/ 1259 T[] getSorted() { 1260 return qsort!(comp)(nodes[0..min(n, nAdded)].dup); 1261 } 1262 } 1263 1264 unittest { 1265 alias TopN!(uint, "a < b") TopNLess; 1266 alias TopN!(uint, "a > b") TopNGreater; 1267 Random gen; 1268 gen.seed(unpredictableSeed); 1269 uint[] nums = new uint[100]; 1270 foreach(i, ref n; nums) { 1271 n = cast(uint) i; 1272 } 1273 foreach(i; 0..100) { 1274 auto less = TopNLess(10); 1275 auto more = TopNGreater(10); 1276 randomShuffle(nums, gen); 1277 foreach(n; nums) { 1278 less.put(n); 1279 more.put(n); 1280 } 1281 assert(less.getSorted == [0U, 1,2,3,4,5,6,7,8,9]); 1282 assert(more.getSorted == [99U, 98, 97, 96, 95, 94, 93, 92, 91, 90]); 1283 } 1284 foreach(i; 0..100) { 1285 auto less = TopNLess(10); 1286 auto more = TopNGreater(10); 1287 randomShuffle(nums, gen); 1288 foreach(n; nums[0..5]) { 1289 less.put(n); 1290 more.put(n); 1291 } 1292 assert(less.getSorted == qsort!("a < b")(nums[0..5])); 1293 assert(more.getSorted == qsort!("a > b")(nums[0..5])); 1294 } 1295 }