github.com/kurthockenbury/dnscontrol@v0.2.8/pkg/natsort/sort.go (about) 1 /* 2 Copyright 2014 Google Inc. All rights reserved. 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 17 // Package natsort provides an implementation of the natural sorting algorithm. 18 // See http://blog.codinghorror.com/sorting-for-humans-natural-sort-order/. 19 package natsort 20 21 import ( 22 "errors" 23 "sort" 24 "strconv" 25 "strings" 26 ) 27 28 // Strings sorts a slice of strings with Less. 29 func Strings(s []string) { 30 sort.Sort(stringSlice(s)) 31 } 32 33 // Less reports whether s is less than t. 34 func Less(s, t string) bool { 35 return LessRunes([]rune(s), []rune(t)) 36 } 37 38 // LessRunes reports whether s is less than t. 39 func LessRunes(s, t []rune) bool { 40 41 nprefix := commonPrefix(s, t) 42 // equal? 43 if len(s) == nprefix && len(t) == nprefix { 44 return false 45 } 46 // empty strings are less than anything. 47 if len(s) == 0 || len(t) == 0 { 48 return len(s) == 0 49 } 50 51 // both start numeric? Sort by the prefixed digits. 52 lds := leadDigits(s) 53 ldt := leadDigits(t) 54 if lds != 0 && ldt != 0 { // both start with a digit 55 if lds == len(s) && ldt == len(t) { // s and t are all digits. 56 if lds == ldt { 57 // equal length? compare as strings. 58 return string(s) < string(t) 59 } 60 // otherwise, the shorter one is smaller. 61 return lds < ldt 62 } 63 ss := string(s[:lds]) 64 st := string(t[:ldt]) 65 ns, _ := strconv.Atoi(ss) 66 nt, _ := strconv.Atoi(st) 67 if ns == nt && lds != ldt { 68 return lds < ldt 69 } 70 return ns < nt 71 } 72 73 // * < digit 74 if strings.HasPrefix(string(s), "*") || strings.HasPrefix(string(t), "*") { 75 if isDigit(t[0]) { 76 return false 77 } 78 if isDigit(s[0]) { 79 return true 80 } 81 return string(s) < string(t) 82 } 83 sps := string(s[nprefix:]) 84 tps := string(t[nprefix:]) 85 if strings.HasPrefix(sps, "-") || strings.HasPrefix(tps, "-") { 86 // digits < - 87 if leadDigits(s[nprefix:]) > 0 && strings.HasPrefix(tps, "-") { 88 return true 89 } 90 if leadDigits(t[nprefix:]) > 0 && strings.HasPrefix(sps, "-") { 91 return false 92 } 93 // . < - 94 if strings.HasPrefix(sps, ".") && strings.HasPrefix(tps, "-") { 95 return false 96 } 97 if strings.HasPrefix(sps, "-") && strings.HasPrefix(tps, ".") { 98 return true 99 } 100 } 101 // digits < . 102 if leadDigits(s[nprefix:]) > 0 && strings.HasPrefix(tps, ".") { 103 return true 104 } 105 if leadDigits(t[nprefix:]) > 0 && strings.HasPrefix(sps, ".") { 106 return false 107 } 108 sEnd := leadDigits(s[nprefix:]) + nprefix 109 tEnd := leadDigits(t[nprefix:]) + nprefix 110 if sEnd > nprefix || tEnd > nprefix { 111 start := trailDigits(s[:nprefix]) 112 if sEnd-start > 0 && tEnd-start > 0 { 113 // TODO(light): log errors? 114 sn := atoi(s[start:sEnd]) 115 tn := atoi(t[start:tEnd]) 116 if sn != tn { 117 return sn < tn 118 } 119 } 120 } 121 switch { 122 case len(s) == nprefix: 123 return true 124 case len(t) == nprefix: 125 return false 126 default: 127 return s[nprefix] < t[nprefix] 128 } 129 } 130 131 func isDigit(r rune) bool { 132 return '0' <= r && r <= '9' 133 } 134 135 func atoi(r []rune) uint64 { 136 if len(r) < 1 { 137 panic(errors.New("atoi got an empty slice")) 138 } 139 const cutoff = uint64((1<<64-1)/10 + 1) 140 const maxVal = 1<<64 - 1 141 142 var n uint64 143 for _, d := range r { 144 v := uint64(d - '0') 145 if n >= cutoff { 146 return 1<<64 - 1 147 } 148 n *= 10 149 n1 := n + v 150 if n1 < n || n1 > maxVal { 151 // n+v overflows 152 return 1<<64 - 1 153 } 154 n = n1 155 } 156 return n 157 } 158 159 func commonPrefix(s, t []rune) int { 160 for i := range s { 161 if i >= len(t) { 162 return len(t) 163 } 164 if s[i] != t[i] { 165 return i 166 } 167 } 168 return len(s) 169 } 170 171 func trailDigits(r []rune) int { 172 for i := len(r) - 1; i >= 0; i-- { 173 if !isDigit(r[i]) { 174 return i + 1 175 } 176 } 177 return 0 178 } 179 180 func leadDigits(r []rune) int { 181 for i := range r { 182 if !isDigit(r[i]) { 183 return i 184 } 185 } 186 return len(r) 187 } 188 189 type stringSlice []string 190 191 func (ss stringSlice) Len() int { 192 return len(ss) 193 } 194 195 func (ss stringSlice) Less(i, j int) bool { 196 return Less(ss[i], ss[j]) 197 } 198 199 func (ss stringSlice) Swap(i, j int) { 200 ss[i], ss[j] = ss[j], ss[i] 201 }