golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/slices/zsortordered.go (about) 1 // Code generated by gen_sort_variants.go; DO NOT EDIT. 2 3 // Copyright 2022 The Go Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file. 6 7 package slices 8 9 import "golang.org/x/exp/constraints" 10 11 // insertionSortOrdered sorts data[a:b] using insertion sort. 12 func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) { 13 for i := a + 1; i < b; i++ { 14 for j := i; j > a && cmpLess(data[j], data[j-1]); j-- { 15 data[j], data[j-1] = data[j-1], data[j] 16 } 17 } 18 } 19 20 // siftDownOrdered implements the heap property on data[lo:hi]. 21 // first is an offset into the array where the root of the heap lies. 22 func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) { 23 root := lo 24 for { 25 child := 2*root + 1 26 if child >= hi { 27 break 28 } 29 if child+1 < hi && cmpLess(data[first+child], data[first+child+1]) { 30 child++ 31 } 32 if !cmpLess(data[first+root], data[first+child]) { 33 return 34 } 35 data[first+root], data[first+child] = data[first+child], data[first+root] 36 root = child 37 } 38 } 39 40 func heapSortOrdered[E constraints.Ordered](data []E, a, b int) { 41 first := a 42 lo := 0 43 hi := b - a 44 45 // Build heap with greatest element at top. 46 for i := (hi - 1) / 2; i >= 0; i-- { 47 siftDownOrdered(data, i, hi, first) 48 } 49 50 // Pop elements, largest first, into end of data. 51 for i := hi - 1; i >= 0; i-- { 52 data[first], data[first+i] = data[first+i], data[first] 53 siftDownOrdered(data, lo, i, first) 54 } 55 } 56 57 // pdqsortOrdered sorts data[a:b]. 58 // The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. 59 // pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf 60 // C++ implementation: https://github.com/orlp/pdqsort 61 // Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ 62 // limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. 63 func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) { 64 const maxInsertion = 12 65 66 var ( 67 wasBalanced = true // whether the last partitioning was reasonably balanced 68 wasPartitioned = true // whether the slice was already partitioned 69 ) 70 71 for { 72 length := b - a 73 74 if length <= maxInsertion { 75 insertionSortOrdered(data, a, b) 76 return 77 } 78 79 // Fall back to heapsort if too many bad choices were made. 80 if limit == 0 { 81 heapSortOrdered(data, a, b) 82 return 83 } 84 85 // If the last partitioning was imbalanced, we need to breaking patterns. 86 if !wasBalanced { 87 breakPatternsOrdered(data, a, b) 88 limit-- 89 } 90 91 pivot, hint := choosePivotOrdered(data, a, b) 92 if hint == decreasingHint { 93 reverseRangeOrdered(data, a, b) 94 // The chosen pivot was pivot-a elements after the start of the array. 95 // After reversing it is pivot-a elements before the end of the array. 96 // The idea came from Rust's implementation. 97 pivot = (b - 1) - (pivot - a) 98 hint = increasingHint 99 } 100 101 // The slice is likely already sorted. 102 if wasBalanced && wasPartitioned && hint == increasingHint { 103 if partialInsertionSortOrdered(data, a, b) { 104 return 105 } 106 } 107 108 // Probably the slice contains many duplicate elements, partition the slice into 109 // elements equal to and elements greater than the pivot. 110 if a > 0 && !cmpLess(data[a-1], data[pivot]) { 111 mid := partitionEqualOrdered(data, a, b, pivot) 112 a = mid 113 continue 114 } 115 116 mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot) 117 wasPartitioned = alreadyPartitioned 118 119 leftLen, rightLen := mid-a, b-mid 120 balanceThreshold := length / 8 121 if leftLen < rightLen { 122 wasBalanced = leftLen >= balanceThreshold 123 pdqsortOrdered(data, a, mid, limit) 124 a = mid + 1 125 } else { 126 wasBalanced = rightLen >= balanceThreshold 127 pdqsortOrdered(data, mid+1, b, limit) 128 b = mid 129 } 130 } 131 } 132 133 // partitionOrdered does one quicksort partition. 134 // Let p = data[pivot] 135 // Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot. 136 // On return, data[newpivot] = p 137 func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { 138 data[a], data[pivot] = data[pivot], data[a] 139 i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned 140 141 for i <= j && cmpLess(data[i], data[a]) { 142 i++ 143 } 144 for i <= j && !cmpLess(data[j], data[a]) { 145 j-- 146 } 147 if i > j { 148 data[j], data[a] = data[a], data[j] 149 return j, true 150 } 151 data[i], data[j] = data[j], data[i] 152 i++ 153 j-- 154 155 for { 156 for i <= j && cmpLess(data[i], data[a]) { 157 i++ 158 } 159 for i <= j && !cmpLess(data[j], data[a]) { 160 j-- 161 } 162 if i > j { 163 break 164 } 165 data[i], data[j] = data[j], data[i] 166 i++ 167 j-- 168 } 169 data[j], data[a] = data[a], data[j] 170 return j, false 171 } 172 173 // partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. 174 // It assumed that data[a:b] does not contain elements smaller than the data[pivot]. 175 func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) { 176 data[a], data[pivot] = data[pivot], data[a] 177 i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned 178 179 for { 180 for i <= j && !cmpLess(data[a], data[i]) { 181 i++ 182 } 183 for i <= j && cmpLess(data[a], data[j]) { 184 j-- 185 } 186 if i > j { 187 break 188 } 189 data[i], data[j] = data[j], data[i] 190 i++ 191 j-- 192 } 193 return i 194 } 195 196 // partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end. 197 func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool { 198 const ( 199 maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted 200 shortestShifting = 50 // don't shift any elements on short arrays 201 ) 202 i := a + 1 203 for j := 0; j < maxSteps; j++ { 204 for i < b && !cmpLess(data[i], data[i-1]) { 205 i++ 206 } 207 208 if i == b { 209 return true 210 } 211 212 if b-a < shortestShifting { 213 return false 214 } 215 216 data[i], data[i-1] = data[i-1], data[i] 217 218 // Shift the smaller one to the left. 219 if i-a >= 2 { 220 for j := i - 1; j >= 1; j-- { 221 if !cmpLess(data[j], data[j-1]) { 222 break 223 } 224 data[j], data[j-1] = data[j-1], data[j] 225 } 226 } 227 // Shift the greater one to the right. 228 if b-i >= 2 { 229 for j := i + 1; j < b; j++ { 230 if !cmpLess(data[j], data[j-1]) { 231 break 232 } 233 data[j], data[j-1] = data[j-1], data[j] 234 } 235 } 236 } 237 return false 238 } 239 240 // breakPatternsOrdered scatters some elements around in an attempt to break some patterns 241 // that might cause imbalanced partitions in quicksort. 242 func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) { 243 length := b - a 244 if length >= 8 { 245 random := xorshift(length) 246 modulus := nextPowerOfTwo(length) 247 248 for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { 249 other := int(uint(random.Next()) & (modulus - 1)) 250 if other >= length { 251 other -= length 252 } 253 data[idx], data[a+other] = data[a+other], data[idx] 254 } 255 } 256 } 257 258 // choosePivotOrdered chooses a pivot in data[a:b]. 259 // 260 // [0,8): chooses a static pivot. 261 // [8,shortestNinther): uses the simple median-of-three method. 262 // [shortestNinther,∞): uses the Tukey ninther method. 263 func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) { 264 const ( 265 shortestNinther = 50 266 maxSwaps = 4 * 3 267 ) 268 269 l := b - a 270 271 var ( 272 swaps int 273 i = a + l/4*1 274 j = a + l/4*2 275 k = a + l/4*3 276 ) 277 278 if l >= 8 { 279 if l >= shortestNinther { 280 // Tukey ninther method, the idea came from Rust's implementation. 281 i = medianAdjacentOrdered(data, i, &swaps) 282 j = medianAdjacentOrdered(data, j, &swaps) 283 k = medianAdjacentOrdered(data, k, &swaps) 284 } 285 // Find the median among i, j, k and stores it into j. 286 j = medianOrdered(data, i, j, k, &swaps) 287 } 288 289 switch swaps { 290 case 0: 291 return j, increasingHint 292 case maxSwaps: 293 return j, decreasingHint 294 default: 295 return j, unknownHint 296 } 297 } 298 299 // order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. 300 func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) { 301 if cmpLess(data[b], data[a]) { 302 *swaps++ 303 return b, a 304 } 305 return a, b 306 } 307 308 // medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. 309 func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int { 310 a, b = order2Ordered(data, a, b, swaps) 311 b, c = order2Ordered(data, b, c, swaps) 312 a, b = order2Ordered(data, a, b, swaps) 313 return b 314 } 315 316 // medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. 317 func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int { 318 return medianOrdered(data, a-1, a, a+1, swaps) 319 } 320 321 func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) { 322 i := a 323 j := b - 1 324 for i < j { 325 data[i], data[j] = data[j], data[i] 326 i++ 327 j-- 328 } 329 } 330 331 func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) { 332 for i := 0; i < n; i++ { 333 data[a+i], data[b+i] = data[b+i], data[a+i] 334 } 335 } 336 337 func stableOrdered[E constraints.Ordered](data []E, n int) { 338 blockSize := 20 // must be > 0 339 a, b := 0, blockSize 340 for b <= n { 341 insertionSortOrdered(data, a, b) 342 a = b 343 b += blockSize 344 } 345 insertionSortOrdered(data, a, n) 346 347 for blockSize < n { 348 a, b = 0, 2*blockSize 349 for b <= n { 350 symMergeOrdered(data, a, a+blockSize, b) 351 a = b 352 b += 2 * blockSize 353 } 354 if m := a + blockSize; m < n { 355 symMergeOrdered(data, a, m, n) 356 } 357 blockSize *= 2 358 } 359 } 360 361 // symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using 362 // the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum 363 // Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz 364 // Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in 365 // Computer Science, pages 714-723. Springer, 2004. 366 // 367 // Let M = m-a and N = b-n. Wolog M < N. 368 // The recursion depth is bound by ceil(log(N+M)). 369 // The algorithm needs O(M*log(N/M + 1)) calls to data.Less. 370 // The algorithm needs O((M+N)*log(M)) calls to data.Swap. 371 // 372 // The paper gives O((M+N)*log(M)) as the number of assignments assuming a 373 // rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation 374 // in the paper carries through for Swap operations, especially as the block 375 // swapping rotate uses only O(M+N) Swaps. 376 // 377 // symMerge assumes non-degenerate arguments: a < m && m < b. 378 // Having the caller check this condition eliminates many leaf recursion calls, 379 // which improves performance. 380 func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { 381 // Avoid unnecessary recursions of symMerge 382 // by direct insertion of data[a] into data[m:b] 383 // if data[a:m] only contains one element. 384 if m-a == 1 { 385 // Use binary search to find the lowest index i 386 // such that data[i] >= data[a] for m <= i < b. 387 // Exit the search loop with i == b in case no such index exists. 388 i := m 389 j := b 390 for i < j { 391 h := int(uint(i+j) >> 1) 392 if cmpLess(data[h], data[a]) { 393 i = h + 1 394 } else { 395 j = h 396 } 397 } 398 // Swap values until data[a] reaches the position before i. 399 for k := a; k < i-1; k++ { 400 data[k], data[k+1] = data[k+1], data[k] 401 } 402 return 403 } 404 405 // Avoid unnecessary recursions of symMerge 406 // by direct insertion of data[m] into data[a:m] 407 // if data[m:b] only contains one element. 408 if b-m == 1 { 409 // Use binary search to find the lowest index i 410 // such that data[i] > data[m] for a <= i < m. 411 // Exit the search loop with i == m in case no such index exists. 412 i := a 413 j := m 414 for i < j { 415 h := int(uint(i+j) >> 1) 416 if !cmpLess(data[m], data[h]) { 417 i = h + 1 418 } else { 419 j = h 420 } 421 } 422 // Swap values until data[m] reaches the position i. 423 for k := m; k > i; k-- { 424 data[k], data[k-1] = data[k-1], data[k] 425 } 426 return 427 } 428 429 mid := int(uint(a+b) >> 1) 430 n := mid + m 431 var start, r int 432 if m > mid { 433 start = n - b 434 r = mid 435 } else { 436 start = a 437 r = m 438 } 439 p := n - 1 440 441 for start < r { 442 c := int(uint(start+r) >> 1) 443 if !cmpLess(data[p-c], data[c]) { 444 start = c + 1 445 } else { 446 r = c 447 } 448 } 449 450 end := n - start 451 if start < m && m < end { 452 rotateOrdered(data, start, m, end) 453 } 454 if a < start && start < mid { 455 symMergeOrdered(data, a, start, mid) 456 } 457 if mid < end && end < b { 458 symMergeOrdered(data, mid, end, b) 459 } 460 } 461 462 // rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: 463 // Data of the form 'x u v y' is changed to 'x v u y'. 464 // rotate performs at most b-a many calls to data.Swap, 465 // and it assumes non-degenerate arguments: a < m && m < b. 466 func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) { 467 i := m - a 468 j := b - m 469 470 for i != j { 471 if i > j { 472 swapRangeOrdered(data, m-i, m, j) 473 i -= j 474 } else { 475 swapRangeOrdered(data, m-i, m+j-i, i) 476 j -= i 477 } 478 } 479 // i == j 480 swapRangeOrdered(data, m-i, m, i) 481 }