github.com/jfcg/sorty/v2@v2.1.0/sorty.go (about)

     1  /*	Copyright (c) 2019, 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 is a type-specific, fast, efficient, concurrent / parallel sorting
     8  // library. It is an innovative [QuickSort] implementation, hence in-place and does not
     9  // require extra memory. You can call:
    10  //
    11  //	import "github.com/jfcg/sorty/v2"
    12  //
    13  //	sorty.SortSlice(native_slice) // []int, []float64, []string, []*T etc. in ascending order
    14  //	sorty.SortLen(len_slice)      // []string or [][]T 'by length' in ascending order
    15  //	sorty.Sort(n, lesswap)        // lesswap() based
    16  //
    17  // [QuickSort]: https://en.wikipedia.org/wiki/Quicksort
    18  package sorty
    19  
    20  import (
    21  	"reflect"
    22  	"unsafe"
    23  
    24  	"github.com/jfcg/sixb"
    25  )
    26  
    27  // MaxGor is the maximum number of goroutines (including caller) that can be
    28  // concurrently used for sorting per Sort*() call. MaxGor can be changed live, even
    29  // during ongoing Sort*() calls. MaxGor ≤ 1 (or a short input) yields single-goroutine
    30  // sorting: sorty will not create any goroutines or channel.
    31  var MaxGor uint64 = 3
    32  
    33  func init() {
    34  	if !(4097 > MaxGor && MaxGor > 0 && MaxLenRec > MaxLenRecFC && MaxLenRecFC >
    35  		2*MaxLenIns && MaxLenIns > MaxLenInsFC && MaxLenInsFC > 2*nsShort) {
    36  		panic("sorty: check your MaxGor/MaxLen* values")
    37  	}
    38  }
    39  
    40  type FloatOption int32
    41  
    42  const (
    43  	NaNsmall FloatOption = iota - 1
    44  	NaNignore
    45  	NaNlarge
    46  )
    47  
    48  // NaNoption determines how sorty handles [NaNs] in [SortSlice]() and [IsSortedSlice]().
    49  // NaNs can be treated as smaller than, ignored or larger than other float values.
    50  // By default NaNs will end up at the end of your ascending-sorted slice. If your slice
    51  // contains NaNs and you choose to ignore them, the result is undefined behavior, and
    52  // almost always not sorted properly. sorty is only tested with small/large options.
    53  //
    54  // [NaNs]: https://en.wikipedia.org/wiki/NaN
    55  var NaNoption = NaNlarge
    56  
    57  // Search returns lowest integer k in [0,n) where fn(k) is true, assuming:
    58  //
    59  //	fn(k) implies fn(k+1)
    60  //
    61  // If there is no such k, it returns n. It can be used to locate an element
    62  // in a sorted collection.
    63  //
    64  //go:nosplit
    65  func Search(n int, fn func(int) bool) int {
    66  	l, h := 0, n
    67  
    68  	for l < h {
    69  		m := sixb.MeanI(l, h)
    70  
    71  		if fn(m) {
    72  			h = m
    73  		} else {
    74  			l = m + 1
    75  		}
    76  	}
    77  	return l
    78  }
    79  
    80  // synchronization variables for [g]long*()
    81  type syncVar struct {
    82  	nGor uint64   // number of sorting goroutines
    83  	done chan int // end signal
    84  }
    85  
    86  // gorFull returns true if goroutine quota is full, inlined
    87  //
    88  //go:norace
    89  func gorFull(sv *syncVar) bool {
    90  	mg := MaxGor
    91  	return sv.nGor >= mg
    92  }
    93  
    94  const (
    95  	// #samples in pivot selection for
    96  	nsShort = 4 // short range
    97  	nsLong  = 6 // long range
    98  	nsConc  = 8 // dual range
    99  )
   100  
   101  // Given n ≥ 2 and slice length ≥ 2n, select n equidistant samples
   102  // from slice that minimizes max distance to non-selected members, inlined
   103  func minMaxSample(slen, n uint) (first, step, last uint) {
   104  	step = slen / n // ≥ 2
   105  	n--
   106  	span := n * step
   107  	tail := slen - span // 1 + #members in both tails
   108  	if tail > n && tail>>1 > (step+1)>>1 {
   109  		step++
   110  		span += n
   111  		tail -= n
   112  	}
   113  	first = tail >> 1 // larger tail
   114  	last = first + span
   115  	return
   116  }
   117  
   118  var firstFour = [8]uint32{0, 0, ^uint32(0), 0, 0, 1, 1, 0}
   119  var stepFour = [8]uint32{0, 0, 1, 1, 0, 0, 0, 1}
   120  
   121  // optimized version of minMaxSample for n=4, inlined
   122  func minMaxFour(slen uint32) (first, step uint32) {
   123  	mod := slen & 7
   124  	first = slen>>3 + firstFour[mod]
   125  	step = slen>>2 + stepFour[mod]
   126  	return
   127  }
   128  
   129  // inlined
   130  func insertionI(slc []int) {
   131  	if unsafe.Sizeof(int(0)) == 8 {
   132  		insertionI8(*(*[]int64)(unsafe.Pointer(&slc)))
   133  	} else {
   134  		insertionI4(*(*[]int32)(unsafe.Pointer(&slc)))
   135  	}
   136  }
   137  
   138  const sliceBias reflect.Kind = 100
   139  
   140  // extracts slice and element kind from ar
   141  //
   142  //go:nosplit
   143  func extractSK(ar any) (slc sixb.Slice, kind reflect.Kind) {
   144  	tipe := reflect.TypeOf(ar)
   145  	if tipe.Kind() != reflect.Slice {
   146  		return
   147  	}
   148  	tipe = tipe.Elem()
   149  	kind = tipe.Kind()
   150  
   151  	switch kind {
   152  	// map int/uint/pointer types to hardware type
   153  	case reflect.Uintptr, reflect.Pointer, reflect.UnsafePointer:
   154  		kind = reflect.Uint32 + reflect.Kind(unsafe.Sizeof(uintptr(0))>>3)
   155  	case reflect.Uint:
   156  		kind = reflect.Uint32 + reflect.Kind(unsafe.Sizeof(uint(0))>>3)
   157  	case reflect.Int:
   158  		kind = reflect.Int32 + reflect.Kind(unsafe.Sizeof(int(0))>>3)
   159  	// map []T to sliceBias + Kind(T)
   160  	case reflect.Slice:
   161  		kind = sliceBias + tipe.Elem().Kind()
   162  	// other recognized types
   163  	case reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64,
   164  		reflect.Float32, reflect.Float64, reflect.String:
   165  	default:
   166  		kind = reflect.Invalid
   167  		return
   168  	}
   169  
   170  	v := reflect.ValueOf(ar)
   171  	p, l := v.Pointer(), v.Len()
   172  	slc = sixb.Slice{Data: unsafe.Pointer(p), Len: l, Cap: l}
   173  	return
   174  }