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.

89 lines
1.9 KiB

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