github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/base/natural_sort.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package base
     7  
     8  import (
     9  	"math/big"
    10  	"unicode/utf8"
    11  )
    12  
    13  // NaturalSortLess compares two strings so that they could be sorted in natural order
    14  func NaturalSortLess(s1, s2 string) bool {
    15  	var i1, i2 int
    16  	for {
    17  		rune1, j1, end1 := getNextRune(s1, i1)
    18  		rune2, j2, end2 := getNextRune(s2, i2)
    19  		if end1 || end2 {
    20  			return end1 != end2 && end1
    21  		}
    22  		dec1 := isDecimal(rune1)
    23  		dec2 := isDecimal(rune2)
    24  		var less, equal bool
    25  		if dec1 && dec2 {
    26  			i1, i2, less, equal = compareByNumbers(s1, i1, s2, i2)
    27  		} else if !dec1 && !dec2 {
    28  			equal = rune1 == rune2
    29  			less = rune1 < rune2
    30  			i1 = j1
    31  			i2 = j2
    32  		} else {
    33  			return rune1 < rune2
    34  		}
    35  		if !equal {
    36  			return less
    37  		}
    38  	}
    39  }
    40  
    41  func getNextRune(str string, pos int) (rune, int, bool) {
    42  	if pos < len(str) {
    43  		r, w := utf8.DecodeRuneInString(str[pos:])
    44  		// Fallback to ascii
    45  		if r == utf8.RuneError {
    46  			r = rune(str[pos])
    47  			w = 1
    48  		}
    49  		return r, pos + w, false
    50  	}
    51  	return 0, pos, true
    52  }
    53  
    54  func isDecimal(r rune) bool {
    55  	return '0' <= r && r <= '9'
    56  }
    57  
    58  func compareByNumbers(str1 string, pos1 int, str2 string, pos2 int) (i1, i2 int, less, equal bool) {
    59  	var d1, d2 bool = true, true
    60  	var dec1, dec2 string
    61  	for d1 || d2 {
    62  		if d1 {
    63  			r, j, end := getNextRune(str1, pos1)
    64  			if !end && isDecimal(r) {
    65  				dec1 += string(r)
    66  				pos1 = j
    67  			} else {
    68  				d1 = false
    69  			}
    70  		}
    71  		if d2 {
    72  			r, j, end := getNextRune(str2, pos2)
    73  			if !end && isDecimal(r) {
    74  				dec2 += string(r)
    75  				pos2 = j
    76  			} else {
    77  				d2 = false
    78  			}
    79  		}
    80  	}
    81  	less, equal = compareBigNumbers(dec1, dec2)
    82  	return pos1, pos2, less, equal
    83  }
    84  
    85  func compareBigNumbers(dec1, dec2 string) (less, equal bool) {
    86  	d1, _ := big.NewInt(0).SetString(dec1, 10)
    87  	d2, _ := big.NewInt(0).SetString(dec2, 10)
    88  	cmp := d1.Cmp(d2)
    89  	return cmp < 0, cmp == 0
    90  }