From 34325f569c30cee73ee9d22fc07d64a3f0fc4b72 Mon Sep 17 00:00:00 2001 From: Jeffrey Faer Date: Thu, 26 Dec 2024 15:14:26 -0700 Subject: [PATCH] refactor: Move comparator functionality to a new file and introduce a CmpFunc type to make things a little more fluent. --- keepsorted/block.go | 66 +++++++++++++------------------------------ keepsorted/cmp.go | 33 ++++++++++++++++++++++ keepsorted/options.go | 6 +--- 3 files changed, 53 insertions(+), 52 deletions(-) create mode 100644 keepsorted/cmp.go diff --git a/keepsorted/block.go b/keepsorted/block.go index 94439d8..fe2210d 100644 --- a/keepsorted/block.go +++ b/keepsorted/block.go @@ -15,7 +15,6 @@ package keepsorted import ( - "cmp" "slices" "strings" @@ -367,31 +366,34 @@ func allHaveSuffix(lgs []lineGroup, s string) bool { return true } -func (b block) lessFn() func(a, b lineGroup) int { +func (b block) lessFn() cmpFunc[lineGroup] { // Always put groups that are only comments last. - commentOnlyBlock := comparingProperty(func(lg lineGroup) int { + commentOnlyBlock := comparing(func(lg lineGroup) int { if len(lg.lines) > 0 { return 0 } return 1 }) - // Check preferred prefixes from longest to shortest. The list of prefixes - // is reversed to assign weights in ascending order: they are multiplied by - // -1 to ensure that entries with matching prefixes are put before any - // non-matching lines (which assume the weight of 0). + // Assign a weight to each prefix so that they will be sorted into their + // predetermined order. + // Weights are negative so that entries with matching prefixes are put before + // any non-matching line (which will have a weight of 0). // - // An empty prefix can be used to move all remaining entries to a position + // An empty prefix can be used to move "non-matching" entries to a position // between other prefixes. + type prefixWeight struct { + prefix string + weight int + } var prefixWeights []prefixWeight for i, p := range b.metadata.opts.PrefixOrder { prefixWeights = append(prefixWeights, prefixWeight{p, i - len(b.metadata.opts.PrefixOrder)}) } - slices.SortStableFunc(prefixWeights, func(a, b prefixWeight) int { - return cmp.Compare(b.prefix, a.prefix) - }) + // Sort prefixes longest -> shortest to find the most appropriate weight. + slices.SortStableFunc(prefixWeights, comparing(func(pw prefixWeight) int { return len(pw.prefix) }).reversed()) - prefixOrder := comparingProperty(func(lg lineGroup) int { + prefixOrder := comparing(func(lg lineGroup) int { for _, w := range prefixWeights { if lg.hasPrefix(w.prefix) { return w.weight @@ -418,7 +420,7 @@ func (b block) lessFn() func(a, b lineGroup) int { // foo_6 // Foo_45 // foo_123 - transformOrder := comparingPropertyWith(func(lg lineGroup) numericTokens { + transformOrder := comparingFunc(func(lg lineGroup) numericTokens { l := lg.joinedLines() if s, ok := b.metadata.opts.removeIgnorePrefix(l); ok { l = s @@ -429,38 +431,8 @@ func (b block) lessFn() func(a, b lineGroup) int { return b.metadata.opts.maybeParseNumeric(l) }, numericTokens.compare) - return func(a, b lineGroup) int { - for _, cmp := range []func(a, b lineGroup) int{ - commentOnlyBlock, - prefixOrder, - transformOrder, - } { - if c := cmp(a, b); c != 0 { - return c - } - } - return a.less(b) - } -} - -func comparingProperty[T any, E cmp.Ordered](f func(T) E) func(a, b T) int { - return comparingPropertyWith(f, func(a, b E) int { - if a < b { - return -1 - } else if a > b { - return 1 - } - return 0 - }) -} - -func comparingPropertyWith[T any, R any](f func(T) R, cmp func(R, R) int) func(a, b T) int { - return func(a, b T) int { - return cmp(f(a), f(b)) - } -} - -type prefixWeight struct { - prefix string - weight int + return commentOnlyBlock. + andThen(prefixOrder). + andThen(transformOrder). + andThen(lineGroup.less) } diff --git a/keepsorted/cmp.go b/keepsorted/cmp.go new file mode 100644 index 0000000..8e623d1 --- /dev/null +++ b/keepsorted/cmp.go @@ -0,0 +1,33 @@ +package keepsorted + +import ( + "cmp" +) + +type cmpFunc[T any] func(T, T) int + +func (f cmpFunc[T]) andThen(next cmpFunc[T]) cmpFunc[T] { + return func(a, b T) int { + if c := f(a, b); c != 0 { + return c + } + return next(a, b) + } +} + +func (f cmpFunc[T]) reversed() cmpFunc[T] { + return func(a, b T) int { + return f(b, a) + } +} + +func comparing[T any, R cmp.Ordered](f func(T) R) cmpFunc[T] { + return comparingFunc(f, cmp.Compare) +} + +func comparingFunc[T, R any](f func(T) R, cmp cmpFunc[R]) cmpFunc[T] { + return func(a, b T) int { + r1, r2 := f(a), f(b) + return cmp(r1, r2) + } +} diff --git a/keepsorted/options.go b/keepsorted/options.go index 223504f..fe68f48 100644 --- a/keepsorted/options.go +++ b/keepsorted/options.go @@ -387,11 +387,7 @@ func (t numericTokens) len() int { } func (t numericTokens) compare(o numericTokens) int { - l := t.len() - if k := o.len(); k < l { - l = k - } - for i := 0; i < l; i++ { + for i := 0; i < min(t.len(), o.len()); i++ { if i%2 == 0 { // Start by comparing strings. if c := strings.Compare(t.s[i/2], o.s[i/2]); c != 0 { return c