一种在Go中对结构片段进行排序的灵活而优雅的方法[关闭]
Let us say that we have a fairly complicated struct
with numerous fields, that I need to sort in several places according to different criteria, e.g.
type MySuperType struct {
x0, x1, x2, x3 xType
// possibly even more fields
}
// sort 1: ascending x0, then descending x1, then more stuff
// sort 2: if x4==0 then applyCriteria2a else applyCriteria2b
func f1(mySuperSlice []MySuperType) {
// sort 'myList' according sort #1
// do something with the sorted list
}
func f2(mySuperSlice []MySuperType) {
// sort 'myList' according sort #2
// do something with the sorted list
}
func f3(mySuperSlice []MySuperType) {
// sort 'myList' according sort #1, note: we use sort #1 again!
// do something with the sorted list
}
Proposed solution 1:
Create a new type (alias of []MySuperType
) that implements sort.Interface
for each sorting criteria required.
Problems:
(i) there is some duplicated code, as the functions Len
and Swap
are going to be identical
(ii) there is going to be a bunch of new types lying around that do not help with the overall readability of the program --- these new types don't really represent anything, plus the only thing that really matters is the Less
function.
Proposed solution 2:
Use sort.Slice
It would be the perfect solution (see this answer), but from my understanding, the sorting function has to be specified inline (I get an error invalid receiver type []T ([]T is an unnamed type)
when I tried to define it elsewhere, which means that I need to define an alias for []T
and we are back to solution 1).
Now, the problem with defining the function inline is that (i) given the complexity of MySuperType
, the function can be very long and (ii) the functions are going to be duplicated in several place (e.g. in f1
and f3
in my example above) -- much more annoying that in solution 1 as the sorting functions can be long and complex.
Note: (i) would not be that much of an issue if we did not have (ii) actually
Question:
Given my current understanding and knowledge of Go, I would use solution 1.
But does anyone knows a different approach that elegantly solves this problem or suggestions to improve the drawbacks listed above?
@ThunderCat's solution will work. Another option would be to write functions that return closures over the slice that match the signature for the less
argument of sort.Slice
:
func ascX0DescX1(s []MySuperType) (func(int, int) bool) {
return func(i, j int) bool {
if s[i].x0 == s[j].x0 {
return s[i].x1 > s[j].x1
}
return s[i].x0 < s[j].x0
}
}
Then pass that as the less
arg to sort.Slice
:
sort.Slice(mySuperSlice, ascX0DescX1(mySuperSlice))
Write sort a sort function for each ordering and call from f1, f2 and f3 as appropriate:
func sortByX0AscX1Desc(s []MySuperType) {
sort.Slice(s, func(i, j int) bool {
switch {
case s[i].x0 < s[j].x0:
return true
case s[i].x0 > s[j].x0:
return false
case s[i].x1 > s[j].x1:
return true
default:
return false
}
})
}
func f1(mySuperSlice []MySuperType) {
sortByX0AscX1Desc(mySuperSlice)
// do something with the sorted list
}
func f2(mySuperSlice []MySuperType) {
sortBySomethingElse(mySuperSlice)
// do something with the sorted list
}
func f3(mySuperSlice []MySuperType) {
sortByX0AscX1Desc(mySuperSlice)
// do something with the sorted list
}
You could also omit the extra function and call sort where you need it.
type MySuperType struct {
x0, x1, x2, x3 string
}
func f1() {
fields := []MySuperType {
{ "a1", "b4", "c3", "d2" },
{ "a2", "b1", "c2", "d3" },
{ "a3", "b1", "c4", "d1" },
{ "a4", "b3", "c1", "d4" },
}
sort.SliceStable(fields, func(i, j int) bool {
return fields[i].x1 < fields[j].x1 || fields[i].x2 > fields[j].x2
})
fmt.Println("by x1, then x2: ", fields)
}
Result: by x1, then x2: [{a3 b1 c4 d1} {a2 b1 c2 d3} {a4 b3 c1 d4} {a1 b4 c3 d2}]