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  }