github.com/hedzr/evendeep@v0.4.8/internal/natsort/natsort.go (about)

     1  // Package natsort implements natural sort. In "Natural Sort Order" integers
     2  // embedded in strings are compared by value.
     3  //
     4  // References:
     5  //    https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
     6  package natsort
     7  
     8  import (
     9  	"sort"
    10  )
    11  
    12  // Strings sorts the given slice of strings in natural order.
    13  func Strings(a []string) {
    14  	sort.Sort(Order(a))
    15  }
    16  
    17  // Order implements sort.Interface to sort strings in natural order. This means
    18  // that e.g. "abc2" < "abc12".
    19  //
    20  // Non-digit sequences and numbers are compared separately. The former are
    21  // compared bytewise, while the latter are compared numerically (except that
    22  // the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02")
    23  //
    24  // Limitation: only ASCII digits (0-9) are considered.
    25  type Order []string
    26  
    27  func (n Order) Len() int           { return len(n) }
    28  func (n Order) Swap(i, j int)      { n[i], n[j] = n[j], n[i] }
    29  func (n Order) Less(i, j int) bool { return Less(n[i], n[j]) }
    30  
    31  // isdigit reports whether the given byte is a decimal digit.
    32  func isdigit(b byte) bool {
    33  	return '0' <= b && b <= '9'
    34  }
    35  
    36  // Less compares two strings using natural ordering.
    37  //
    38  // This means that e.g. "abc2" < "abc12".
    39  //
    40  // Non-digit sequences and numbers are compared separately. The former are
    41  // compared bytewise, while the latter are compared numerically (except that
    42  // the number of leading zeros is used as a tie-breaker).
    43  // E.g. "2" < "02", "1b" > "1ax", ...
    44  //
    45  // Limitation: only ASCII digits (0-9) are considered.
    46  func Less(str1, str2 string) bool {
    47  	idx1, idx2 := 0, 0
    48  	for idx1 < len(str1) && idx2 < len(str2) {
    49  		c1, c2 := str1[idx1], str2[idx2]
    50  		dig1, dig2 := isdigit(c1), isdigit(c2)
    51  		switch {
    52  		case dig1 && dig2: // Digits
    53  			return digitsLess(str1, str2, idx1, idx2)
    54  
    55  		default: // non-digit characters
    56  			// UTF-8 compares bytewise-lexicographically, no need to decode
    57  			// codepoints.
    58  			if c1 != c2 {
    59  				return c1 < c2
    60  			}
    61  			idx1++
    62  			idx2++
    63  		}
    64  		// They're identical so far, so continue comparing.
    65  	}
    66  	// So far they are identical. At least one is ended. If the other continues,
    67  	// it sorts last.
    68  	return len(str1) < len(str2)
    69  }
    70  
    71  func digitsLess(str1, str2 string, idx1, idx2 int) bool {
    72  	// Eat zeros.
    73  	for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ {
    74  	}
    75  	for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ {
    76  	}
    77  	// Eat all digits.
    78  	nonZero1, nonZero2 := idx1, idx2
    79  	for ; idx1 < len(str1) && isdigit(str1[idx1]); idx1++ {
    80  	}
    81  	for ; idx2 < len(str2) && isdigit(str2[idx2]); idx2++ {
    82  	}
    83  	// If lengths of numbers with non-zero prefix differ, the shorter
    84  	// one is less.
    85  	if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 {
    86  		return len1 < len2
    87  	}
    88  	// If they're equal, string comparison is correct.
    89  	if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 {
    90  		return nr1 < nr2
    91  	}
    92  	// Otherwise, the one with less zeros is less.
    93  	// Because everything up to the number is equal, comparing the index
    94  	// after the zeros is sufficient.
    95  	if nonZero1 != nonZero2 {
    96  		return nonZero1 < nonZero2
    97  	}
    98  	return len(str1) < len(str2)
    99  }