Skip to content

Commit ebff37a

Browse files
authored
Improve natural sort (#29611)
Hugely simplify the code, and add more tests (only new approach could pass)
1 parent 3f3335a commit ebff37a

File tree

2 files changed

+12
-78
lines changed

2 files changed

+12
-78
lines changed

modules/base/natural_sort.go

Lines changed: 4 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -4,85 +4,12 @@
44
package base
55

66
import (
7-
"math/big"
8-
"unicode/utf8"
7+
"golang.org/x/text/collate"
8+
"golang.org/x/text/language"
99
)
1010

1111
// NaturalSortLess compares two strings so that they could be sorted in natural order
1212
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
13+
c := collate.New(language.English, collate.Numeric)
14+
return c.CompareString(s1, s2) < 0
8815
}

modules/base/natural_sort_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
func TestNaturalSortLess(t *testing.T) {
1313
test := func(s1, s2 string, less bool) {
14-
assert.Equal(t, less, NaturalSortLess(s1, s2))
14+
assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2)
1515
}
1616
test("v1.20.0", "v1.2.0", false)
1717
test("v1.20.0", "v1.29.0", true)
@@ -20,4 +20,11 @@ func TestNaturalSortLess(t *testing.T) {
2020
test("a-1-a", "a-1-b", true)
2121
test("2", "12", true)
2222
test("a", "ab", true)
23+
24+
test("A", "b", true)
25+
test("a", "B", true)
26+
27+
test("cafe", "café", true)
28+
test("café", "cafe", false)
29+
test("caff", "café", false)
2330
}

0 commit comments

Comments
 (0)