You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

natural_sort.go 1.8KB

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