diff --git a/go.mod b/go.mod index 32363b994..71569aa54 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/lomik/og-rek v0.0.0-20170411191824-628eefeb8d80 github.com/lomik/zapwriter v0.0.0-20210624082824-c1161d1eb463 - github.com/maruel/natural v1.1.1 + github.com/maruel/natural v1.3.0 github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 github.com/msaf1980/go-metrics v0.0.14 github.com/msaf1980/go-stringutils v0.1.6 diff --git a/go.sum b/go.sum index d0445f0fa..5449b56ea 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,8 @@ github.com/lomik/og-rek v0.0.0-20170411191824-628eefeb8d80 h1:KVyDGUXjVOdHQt24wI github.com/lomik/og-rek v0.0.0-20170411191824-628eefeb8d80/go.mod h1:T7SQVaLtK7mcQIEVzveZVJzsDQpAtzTs2YoezrIBdvI= github.com/lomik/zapwriter v0.0.0-20210624082824-c1161d1eb463 h1:SN/0TEkyYpp8tit79JPUnecebCGZsXiYYPxN8i3I6Rk= github.com/lomik/zapwriter v0.0.0-20210624082824-c1161d1eb463/go.mod h1:rWIJAUD2hPOAyOzc3jBShAhN4CAZeLAyzUA/n8tE8ak= -github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= -github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg= +github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk= github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU= github.com/msaf1980/go-metrics v0.0.14 h1:gD0kCG5MDbon33Nkz49yW6kz3yu0DHzDN0SxjGTWlTA= diff --git a/vendor/github.com/maruel/natural/natsort.go b/vendor/github.com/maruel/natural/natural.go similarity index 54% rename from vendor/github.com/maruel/natural/natsort.go rename to vendor/github.com/maruel/natural/natural.go index df3ee0cba..92122fe42 100644 --- a/vendor/github.com/maruel/natural/natsort.go +++ b/vendor/github.com/maruel/natural/natural.go @@ -12,6 +12,7 @@ package natural import ( "strconv" + "strings" ) // Less does a 'natural' comparison on the two strings. @@ -20,13 +21,22 @@ import ( // // This function does no memory allocation. func Less(a, b string) bool { + return Compare(a, b) < 0 +} + +// Compare does a 'natural' comparison on the two strings. +// +// It treats digits as decimal numbers, so that Compare("10", "2") return >0. +// +// This function does no memory allocation. +func Compare(a, b string) int { for { if p := commonPrefix(a, b); p != 0 { a = a[p:] b = b[p:] } if len(a) == 0 { - return len(b) != 0 + return -len(b) } if ia := digits(a); ia > 0 { if ib := digits(b); ib > 0 { @@ -34,8 +44,10 @@ func Less(a, b string) bool { an, aerr := strconv.ParseUint(a[:ia], 10, 64) bn, berr := strconv.ParseUint(b[:ib], 10, 64) if aerr == nil && berr == nil { + // Fast path: both fit in uint64 if an != bn { - return an < bn + // #nosec G40 + return int(an - bn) } // Semantically the same digits, e.g. "00" == "0", "01" == "1". In // this case, only continue processing if there's trailing data on @@ -45,15 +57,30 @@ func Less(a, b string) bool { b = b[ib:] continue } + } else { + // Slow path: at least one number exceeds uint64 + // Both are still pure digits (verified by ia > 0 and ib > 0) + result := compareNumericStrings(a[:ia], b[:ib]) + if result != 0 { + return result + } + // Numbers are semantically equal, continue if both have trailing data + if ia != len(a) && ib != len(b) { + a = a[ia:] + b = b[ib:] + continue + } } } } - return a < b + return strings.Compare(a, b) } } // StringSlice attaches the methods of Interface to []string, sorting in // increasing order using natural order. +// +// It is now obsolete, use slices.Sort() along with natural.Compare instead. type StringSlice []string func (p StringSlice) Len() int { return len(p) } @@ -92,3 +119,36 @@ func digits(s string) int { } return len(s) } + +// compareNumericStrings compares two numeric strings without parsing them. +// This handles arbitrarily large numbers that don't fit in uint64. +// It does no memory allocation. +func compareNumericStrings(a, b string) int { + // Strip leading zeros + a = trimLeadingZeros(a) + b = trimLeadingZeros(b) + + // Compare by length first (more digits = larger number) + if len(a) != len(b) { + return len(a) - len(b) + } + + // Same length: lexical comparison works correctly for digits + return strings.Compare(a, b) +} + +// trimLeadingZeros removes leading zeros from a numeric string. +// Returns "0" if the string is all zeros. +// This function does no memory allocation (only string slicing). +func trimLeadingZeros(s string) string { + for i := 0; i < len(s); i++ { + if s[i] != '0' { + return s[i:] + } + } + // All zeros - return "0" + if len(s) > 0 { + return s[len(s)-1:] + } + return s +} diff --git a/vendor/modules.txt b/vendor/modules.txt index aa6f0ffa0..48a76cd8c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -101,7 +101,7 @@ github.com/lomik/og-rek # github.com/lomik/zapwriter v0.0.0-20210624082824-c1161d1eb463 ## explicit; go 1.16 github.com/lomik/zapwriter -# github.com/maruel/natural v1.1.1 +# github.com/maruel/natural v1.3.0 ## explicit; go 1.21 github.com/maruel/natural # github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12