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 }