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  }