From ebff37ae09e99126668cbee5804d45f069a42081 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 5 Mar 2024 23:13:35 +0800 Subject: [PATCH] Improve natural sort (#29611) Hugely simplify the code, and add more tests (only new approach could pass) --- modules/base/natural_sort.go | 81 ++----------------------------- modules/base/natural_sort_test.go | 9 +++- 2 files changed, 12 insertions(+), 78 deletions(-) diff --git a/modules/base/natural_sort.go b/modules/base/natural_sort.go index e920177f89..0f90ec70ce 100644 --- a/modules/base/natural_sort.go +++ b/modules/base/natural_sort.go @@ -4,85 +4,12 @@ package base import ( - "math/big" - "unicode/utf8" + "golang.org/x/text/collate" + "golang.org/x/text/language" ) // NaturalSortLess compares two strings so that they could be sorted in natural order func NaturalSortLess(s1, s2 string) bool { - var i1, i2 int - for { - rune1, j1, end1 := getNextRune(s1, i1) - rune2, j2, end2 := getNextRune(s2, i2) - if end1 || end2 { - return end1 != end2 && end1 - } - dec1 := isDecimal(rune1) - dec2 := isDecimal(rune2) - var less, equal bool - if dec1 && dec2 { - i1, i2, less, equal = compareByNumbers(s1, i1, s2, i2) - } else if !dec1 && !dec2 { - equal = rune1 == rune2 - less = rune1 < rune2 - i1 = j1 - i2 = j2 - } else { - return rune1 < rune2 - } - if !equal { - return less - } - } -} - -func getNextRune(str string, pos int) (rune, int, bool) { - if pos < len(str) { - r, w := utf8.DecodeRuneInString(str[pos:]) - // Fallback to ascii - if r == utf8.RuneError { - r = rune(str[pos]) - w = 1 - } - return r, pos + w, false - } - return 0, pos, true -} - -func isDecimal(r rune) bool { - return '0' <= r && r <= '9' -} - -func compareByNumbers(str1 string, pos1 int, str2 string, pos2 int) (i1, i2 int, less, equal bool) { - d1, d2 := true, true - var dec1, dec2 string - for d1 || d2 { - if d1 { - r, j, end := getNextRune(str1, pos1) - if !end && isDecimal(r) { - dec1 += string(r) - pos1 = j - } else { - d1 = false - } - } - if d2 { - r, j, end := getNextRune(str2, pos2) - if !end && isDecimal(r) { - dec2 += string(r) - pos2 = j - } else { - d2 = false - } - } - } - less, equal = compareBigNumbers(dec1, dec2) - return pos1, pos2, less, equal -} - -func compareBigNumbers(dec1, dec2 string) (less, equal bool) { - d1, _ := big.NewInt(0).SetString(dec1, 10) - d2, _ := big.NewInt(0).SetString(dec2, 10) - cmp := d1.Cmp(d2) - return cmp < 0, cmp == 0 + c := collate.New(language.English, collate.Numeric) + return c.CompareString(s1, s2) < 0 } diff --git a/modules/base/natural_sort_test.go b/modules/base/natural_sort_test.go index 91e864ad2a..f27a4eb53a 100644 --- a/modules/base/natural_sort_test.go +++ b/modules/base/natural_sort_test.go @@ -11,7 +11,7 @@ import ( func TestNaturalSortLess(t *testing.T) { test := func(s1, s2 string, less bool) { - assert.Equal(t, less, NaturalSortLess(s1, s2)) + assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2) } test("v1.20.0", "v1.2.0", false) test("v1.20.0", "v1.29.0", true) @@ -20,4 +20,11 @@ func TestNaturalSortLess(t *testing.T) { test("a-1-a", "a-1-b", true) test("2", "12", true) test("a", "ab", true) + + test("A", "b", true) + test("a", "B", true) + + test("cafe", "café", true) + test("café", "cafe", false) + test("caff", "café", false) }