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  }