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  }