github.com/jfcg/sorty@v1.2.0/sortyLenS.go (about) 1 /* Copyright (c) 2021, Serhat Şevki Dinçer. 2 This Source Code Form is subject to the terms of the Mozilla Public 3 License, v. 2.0. If a copy of the MPL was not distributed with this 4 file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 */ 6 7 package sorty 8 9 import "sync/atomic" 10 11 // insertion sort, assumes len(ar) >= 2 12 func insertionLenS(ar []string) { 13 hi := len(ar) - 1 14 for l, h := (hi-3)>>1, hi; l >= 0; { 15 if len(ar[h]) < len(ar[l]) { 16 ar[l], ar[h] = ar[h], ar[l] 17 } 18 l-- 19 h-- 20 } 21 for h := 0; ; { 22 l := h 23 h++ 24 v := ar[h] 25 if len(v) < len(ar[l]) { 26 for { 27 ar[l+1] = ar[l] 28 l-- 29 if l < 0 || len(v) >= len(ar[l]) { 30 break 31 } 32 } 33 ar[l+1] = v 34 } 35 if h >= hi { 36 break 37 } 38 } 39 } 40 41 // pivotLenS divides ar into 2n+1 equal intervals, sorts mid-points of them 42 // to find median-of-2n+1 pivot. ensures lo/hi ranges have at least n elements by 43 // moving 2n of mid-points to n positions at lo/hi ends. 44 // assumes n > 0, len(ar) > 4n+2. returns remaining slice,pivot for partitioning. 45 func pivotLenS(ar []string, n int) ([]string, int) { 46 m := len(ar) >> 1 47 s := len(ar) / (2*n + 1) // step > 1 48 l, h := m-n*s, m+n*s 49 50 for q, k := h, m-2*s; k >= l; { // insertion sort ar[m+i*s], i=-n..n 51 if len(ar[q]) < len(ar[k]) { 52 ar[k], ar[q] = ar[q], ar[k] 53 } 54 q -= s 55 k -= s 56 } 57 for q := l; ; { 58 k := q 59 q += s 60 v := ar[q] 61 if len(v) < len(ar[k]) { 62 for { 63 ar[k+s] = ar[k] 64 k -= s 65 if k < l || len(v) >= len(ar[k]) { 66 break 67 } 68 } 69 ar[k+s] = v 70 } 71 if q >= h { 72 break 73 } 74 } 75 76 lo, hi := 0, len(ar) 77 78 // move lo/hi mid-points to lo/hi ends 79 for { 80 hi-- 81 ar[l], ar[lo] = ar[lo], ar[l] 82 ar[h], ar[hi] = ar[hi], ar[h] 83 l += s 84 h -= s 85 lo++ 86 if h <= m { 87 break 88 } 89 } 90 91 return ar[lo:hi:hi], len(ar[m]) // lo <= m-s+1, m+s-1 < hi 92 } 93 94 // partition ar into <= and >= pivot, assumes len(ar) >= 2 95 // returns k with ar[:k] <= pivot, ar[k:] >= pivot 96 func partition1LenS(ar []string, pv int) int { 97 l, h := 0, len(ar)-1 98 for { 99 if len(ar[h]) < pv { // avoid unnecessary comparisons 100 for { 101 if pv < len(ar[l]) { 102 ar[l], ar[h] = ar[h], ar[l] 103 break 104 } 105 l++ 106 if l >= h { 107 return l + 1 108 } 109 } 110 } else if pv < len(ar[l]) { // extend ranges in balance 111 for { 112 h-- 113 if l >= h { 114 return l 115 } 116 if len(ar[h]) < pv { 117 ar[l], ar[h] = ar[h], ar[l] 118 break 119 } 120 } 121 } 122 l++ 123 h-- 124 if l >= h { 125 break 126 } 127 } 128 if l == h && len(ar[h]) < pv { // classify mid element 129 l++ 130 } 131 return l 132 } 133 134 // rearrange ar[:a] and ar[b:] into <= and >= pivot, assumes 0 < a < b < len(ar) 135 // gap (a,b) expands until one of the intervals is fully consumed 136 func partition2LenS(ar []string, a, b, pv int) (int, int) { 137 a-- 138 for { 139 if len(ar[b]) < pv { // avoid unnecessary comparisons 140 for { 141 if pv < len(ar[a]) { 142 ar[a], ar[b] = ar[b], ar[a] 143 break 144 } 145 a-- 146 if a < 0 { 147 return a, b 148 } 149 } 150 } else if pv < len(ar[a]) { // extend ranges in balance 151 for { 152 b++ 153 if b >= len(ar) { 154 return a, b 155 } 156 if len(ar[b]) < pv { 157 ar[a], ar[b] = ar[b], ar[a] 158 break 159 } 160 } 161 } 162 a-- 163 b++ 164 if a < 0 || b >= len(ar) { 165 return a, b 166 } 167 } 168 } 169 170 // new-goroutine partition 171 func gpart1LenS(ar []string, pv int, ch chan int) { 172 ch <- partition1LenS(ar, pv) 173 } 174 175 // concurrent dual partitioning of ar 176 // returns k with ar[:k] <= pivot, ar[k:] >= pivot 177 func cdualparLenS(ar []string, ch chan int) int { 178 179 aq, pv := pivotLenS(ar, 4) // median-of-9 180 k := len(aq) >> 1 181 a, b := k>>1, mid(k, len(aq)) 182 183 go gpart1LenS(aq[a:b:b], pv, ch) // mid half range 184 185 t := a 186 a, b = partition2LenS(aq, a, b, pv) // left/right quarter ranges 187 k = <-ch 188 k += t // convert k indice to aq 189 190 // only one gap is possible 191 for ; 0 <= a; a-- { // gap left in low range? 192 if pv < len(aq[a]) { 193 k-- 194 aq[a], aq[k] = aq[k], aq[a] 195 } 196 } 197 for ; b < len(aq); b++ { // gap left in high range? 198 if len(aq[b]) < pv { 199 aq[b], aq[k] = aq[k], aq[b] 200 k++ 201 } 202 } 203 return k + 4 // convert k indice to ar 204 } 205 206 // short range sort function, assumes Mli < len(ar) <= Mlr 207 func shortLenS(ar []string) { 208 start: 209 aq, pv := pivotLenS(ar, 2) 210 k := partition1LenS(aq, pv) // median-of-5 partitioning 211 212 k += 2 // convert k indice from aq to ar 213 214 if k < len(ar)-k { 215 aq = ar[:k:k] 216 ar = ar[k:] // ar is the longer range 217 } else { 218 aq = ar[k:] 219 ar = ar[:k:k] 220 } 221 222 if len(aq) > Mli { 223 shortLenS(aq) // recurse on the shorter range 224 goto start 225 } 226 insertionLenS(aq) // at least one insertion range 227 228 if len(ar) > Mli { 229 goto start 230 } 231 insertionLenS(ar) // two insertion ranges 232 } 233 234 // long range sort function (single goroutine), assumes len(ar) > Mlr 235 func slongLenS(ar []string) { 236 start: 237 aq, pv := pivotLenS(ar, 3) 238 k := partition1LenS(aq, pv) // median-of-7 partitioning 239 240 k += 3 // convert k indice from aq to ar 241 242 if k < len(ar)-k { 243 aq = ar[:k:k] 244 ar = ar[k:] // ar is the longer range 245 } else { 246 aq = ar[k:] 247 ar = ar[:k:k] 248 } 249 250 if len(aq) > Mlr { // at least one not-long range? 251 slongLenS(aq) // recurse on the shorter range 252 goto start 253 } 254 255 if len(aq) > Mli { 256 shortLenS(aq) 257 } else { 258 insertionLenS(aq) 259 } 260 261 if len(ar) > Mlr { // two not-long ranges? 262 goto start 263 } 264 shortLenS(ar) // we know len(ar) > Mli 265 } 266 267 // new-goroutine sort function 268 func glongLenS(ar []string, sv *syncVar) { 269 longLenS(ar, sv) 270 271 if atomic.AddUint32(&sv.ngr, ^uint32(0)) == 0 { // decrease goroutine counter 272 sv.done <- 0 // we are the last, all done 273 } 274 } 275 276 // long range sort function, assumes len(ar) > Mlr 277 func longLenS(ar []string, sv *syncVar) { 278 start: 279 aq, pv := pivotLenS(ar, 3) 280 k := partition1LenS(aq, pv) // median-of-7 partitioning 281 282 k += 3 // convert k indice from aq to ar 283 284 if k < len(ar)-k { 285 aq = ar[:k:k] 286 ar = ar[k:] // ar is the longer range 287 } else { 288 aq = ar[k:] 289 ar = ar[:k:k] 290 } 291 292 // branches below are optimal for fewer total jumps 293 if len(aq) <= Mlr { // at least one not-long range? 294 295 if len(aq) > Mli { 296 shortLenS(aq) 297 } else { 298 insertionLenS(aq) 299 } 300 301 if len(ar) > Mlr { // two not-long ranges? 302 goto start 303 } 304 shortLenS(ar) // we know len(ar) > Mli 305 return 306 } 307 308 // max goroutines? not atomic but good enough 309 if sv.ngr >= Mxg { 310 longLenS(aq, sv) // recurse on the shorter range 311 goto start 312 } 313 314 if atomic.AddUint32(&sv.ngr, 1) == 0 { // increase goroutine counter 315 panic("sorty: longLenS: counter overflow") 316 } 317 // new-goroutine sort on the longer range only when 318 // both ranges are big and max goroutines is not exceeded 319 go glongLenS(ar, sv) 320 ar = aq 321 goto start 322 } 323 324 // sortLenS concurrently sorts ar by length in ascending order. 325 func sortLenS(ar []string) { 326 327 if len(ar) < 2*(Mlr+1) || Mxg <= 1 { 328 329 // single-goroutine sorting 330 if len(ar) > Mlr { 331 slongLenS(ar) 332 } else if len(ar) > Mli { 333 shortLenS(ar) 334 } else if len(ar) > 1 { 335 insertionLenS(ar) 336 } 337 return 338 } 339 340 // create channel only when concurrent partitioning & sorting 341 sv := syncVar{1, // number of goroutines including this 342 make(chan int)} // end signal 343 for { 344 // median-of-9 concurrent dual partitioning with done 345 k := cdualparLenS(ar, sv.done) 346 var aq []string 347 348 if k < len(ar)-k { 349 aq = ar[:k:k] 350 ar = ar[k:] // ar is the longer range 351 } else { 352 aq = ar[k:] 353 ar = ar[:k:k] 354 } 355 356 // handle shorter range 357 if len(aq) > Mlr { 358 if atomic.AddUint32(&sv.ngr, 1) == 0 { // increase goroutine counter 359 panic("sorty: sortLenS: counter overflow") 360 } 361 go glongLenS(aq, &sv) 362 363 } else if len(aq) > Mli { 364 shortLenS(aq) 365 } else { 366 insertionLenS(aq) 367 } 368 369 // longer range big enough? max goroutines? 370 if len(ar) < 2*(Mlr+1) || sv.ngr >= Mxg { 371 break 372 } 373 // dual partition longer range 374 } 375 376 longLenS(ar, &sv) // we know len(ar) > Mlr 377 378 if atomic.AddUint32(&sv.ngr, ^uint32(0)) != 0 { // decrease goroutine counter 379 <-sv.done // we are not the last, wait 380 } 381 }