code.gitea.io/gitea@v1.19.3/modules/base/natural_sort.go (about)

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