github.com/coreos/mantle@v0.13.0/lang/natsort/cmp.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // Copyright 2000 Martin Pool <mbp@humbug.org.au> 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 // natsort implements Martin Pool's Natural Order String Comparison 17 // Original implementation: https://github.com/sourcefrog/natsort 18 // 19 // Strings are sorted as usual, except that decimal integer substrings 20 // are compared on their numeric value. For example: 21 // 22 // a < a0 < a1 < a1a < a1b < a2 < a10 < a20 23 // 24 // All white space and control characters are ignored. 25 // 26 // Leading zeros are *not* ignored, which tends to give more 27 // reasonable results on decimal fractions: 28 // 29 // 1.001 < 1.002 < 1.010 < 1.02 < 1.1 < 1.3 30 // 31 package natsort 32 33 func isDigit(s string, i int) bool { 34 return i < len(s) && s[i] >= '0' && s[i] <= '9' 35 } 36 37 // Compare unpadded numbers, such as a plain ol' integer. 38 func cmpInteger(a, b string, ai, bi *int) int { 39 // The longest run of digits wins. That aside, the greatest 40 // value wins, but we can't know that it will until we've 41 // scanned both numbers to know that they have the same 42 // magnitude, so we remember it in bias. 43 var bias int 44 for { 45 aIsDigit := isDigit(a, *ai) 46 bIsDigit := isDigit(b, *bi) 47 switch { 48 case !aIsDigit && !bIsDigit: 49 return bias 50 case !aIsDigit: 51 return -1 52 case !bIsDigit: 53 return +1 54 case bias == 0 && a[*ai] < b[*bi]: 55 bias = -1 56 case bias == 0 && a[*ai] > b[*bi]: 57 bias = +1 58 } 59 *ai++ 60 *bi++ 61 } 62 } 63 64 // Compare zero padded numbers, such as the fractional part of a decimal. 65 func cmpFraction(a, b string, ai, bi *int) int { 66 for { 67 aIsDigit := isDigit(a, *ai) 68 bIsDigit := isDigit(b, *bi) 69 switch { 70 case !aIsDigit && !bIsDigit: 71 return 0 72 case !aIsDigit: 73 return -1 74 case !bIsDigit: 75 return +1 76 case a[*ai] < b[*bi]: 77 return -1 78 case a[*ai] > b[*bi]: 79 return +1 80 } 81 *ai++ 82 *bi++ 83 } 84 } 85 86 // Compare tests if a is less than, equal to, or greater than b according 87 // to the natural sorting algorithm, returning -1, 0, or +1 respectively. 88 func Compare(a, b string) int { 89 var ai, bi int 90 for { 91 // Skip ASCII space and control characters. Keeping it simple 92 // for the sake of speed. Checking specific chars and UTF-8 93 // more than doubles the runtime for non-numeric strings. 94 for ai < len(a) && a[ai] <= 32 { 95 ai++ 96 } 97 for bi < len(b) && b[bi] <= 32 { 98 bi++ 99 } 100 101 if isDigit(a, ai) && isDigit(b, bi) { 102 if a[ai] == '0' || b[bi] == '0' { 103 if r := cmpFraction(a, b, &ai, &bi); r != 0 { 104 return r 105 } 106 } else { 107 if r := cmpInteger(a, b, &ai, &bi); r != 0 { 108 return r 109 } 110 } 111 } 112 113 switch { 114 case ai >= len(a) && bi >= len(b): 115 return 0 116 case ai >= len(a): 117 return -1 118 case bi >= len(b): 119 return +1 120 case a[ai] < b[bi]: 121 return -1 122 case a[ai] > b[bi]: 123 return +1 124 } 125 126 ai++ 127 bi++ 128 } 129 }