How can I combine sorters in Go? For example first I need sort by number of comments but if number of comments is null I need sort alphabetically.
This is what I have tried.
func sortArticles(articles []*Article) []*Article {
topArticlesSlice := make([]*Article, 0)
topArticlesSlice = append(topArticlesSlice, articles[:]...)
sort.SliceStable(topArticlesSlice, func(i, j int) bool {
var sortedByNumComments, areNumCommentsEquals, sortedByName bool
if topArticlesSlice[i].NumComments != nil && topArticlesSlice[j].NumComments != nil {
areNumCommentsEquals = *topArticlesSlice[i].NumComments == *topArticlesSlice[j].NumComments
sortedByNumComments = *topArticlesSlice[i].NumComments > *topArticlesSlice[j].NumComments
}
if areNumCommentsEquals {
if topArticlesSlice[i].Title != nil && topArticlesSlice[j].Title != nil {
sortedByName = *topArticlesSlice[i].Title == *topArticlesSlice[j].Title
return sortedByName
} else if topArticlesSlice[i].StoryTitle != nil && topArticlesSlice[j].StoryTitle != nil {
sortedByName = *topArticlesSlice[i].StoryTitle == *topArticlesSlice[j].StoryTitle
return sortedByName
}
return false
}
return sortedByNumComments
})
return topArticlesSlice
}
CodePudding user response:
package main
import (
"fmt"
"sort"
)
type Article struct {
Title string
NumComments int
}
func main() {
a1 := Article{"Dog", 3}
a2 := Article{"Tiger", 0}
a3 := Article{"Cat", 4}
a4 := Article{"Fish", 0}
a5 := Article{"Whale", 8}
articles := []Article{a1, a2, a3, a4, a5}
sort.Slice(articles, func(i, j int) bool {
if articles[i].NumComments == 0 && articles[j].NumComments == 0 {
return articles[i].Title < articles[j].Title
} else {
return articles[i].NumComments < articles[j].NumComments
}
})
fmt.Printf("articles: %v\n", articles)
}
Some of the type definitions are missing in your post. I have taken a simple strut example. I think this is what you may be looking for?
CodePudding user response:
Assuming I'm understanding your requirement correctly you can use something like the following:
func sortArticles(articles []*Article) []*Article {
topArticlesSlice := append([]*Article{}, articles[:]...)
sort.SliceStable(topArticlesSlice, func(i, j int) bool {
if topArticlesSlice[i].NumComments != nil && topArticlesSlice[j].NumComments != nil &&
*topArticlesSlice[i].NumComments != *topArticlesSlice[j].NumComments {
return *topArticlesSlice[i].NumComments < *topArticlesSlice[j].NumComments
}
if topArticlesSlice[i].Title != nil && topArticlesSlice[j].Title != nil &&
*topArticlesSlice[i].Title != *topArticlesSlice[j].Title {
return *topArticlesSlice[i].Title < *topArticlesSlice[j].Title
} else if topArticlesSlice[i].StoryTitle != nil && topArticlesSlice[j].StoryTitle != nil {
return *topArticlesSlice[i].StoryTitle < *topArticlesSlice[j].StoryTitle
}
return false
})
return topArticlesSlice
}
Try this in the playground.
CodePudding user response:
Your compare function is far too complex. You need to refactor it into simpler more straightforward bits.
And, you haven't defined what your Article
type looks like, so, for the purposes of example, I'm going to define it thus:
type Article struct {
NumComments *int
Title *string
}
Your basic ask is that you want to sort, first by the number of comments, and then (if the number of comments is nil) alphabetically by title, correct?
From your original code, it would appear that
NumComments
is a pointer to int (*int
), andTitle
is a pointer to string (*string
)
That means that each comparison has four cases that have to be dealt with:
X | Y | Action |
---|---|---|
non-nil | non-nil | Compare x and y (according to their underlying type) |
non-nil | nil | How does nil compare with non-nil? (implementation detail) |
nil | non-nil | How does nil compare with non-nil? (implementation detail) |
nil | nil | two nils compare equal for the purposes of collation |
For the purposes of this exercise, I'm going to declare that nil collates high with respect to non-nil (but nil collating low with respect to non-nil is equally valid. An implementation choice).
Comparing 2 *int
values is easy:
func compareIntPtr(x *int, y *int) int {
var cc int
switch {
case x != nil && y != nil: cc = sign(*x - *y)
case x == nil && y == nil: cc = 0
case x == nil && y != nil: cc = 1
case x != nil && y == nil: cc = -1
}
return cc
}
func sign(n int) int {
var sign int
switch {
case n < 0: sign = -1
case n > 0: sign = 1
default: sign = 0
}
return sign
}
As is comparing two *string
values:
import "strings"
.
.
.
func compareStringPtr(x *string, y *string) int {
var cc int
switch {
case x != nil && y != nil: cc = strings.Compare(*x, *y)
case x == nil && y == nil: cc = 0
case x == nil && y != nil: cc = 1
case x != nil && y == nil: cc = -1
}
return cc
}
Once you have those primitives, the comparer function for the sort is even simpler:
func sortArticles(articles []*Article) []*Article {
topArticlesSlice := make([]*Article, 0)
topArticlesSlice = append(topArticlesSlice, articles[:]...)
sort.SliceStable(topArticlesSlice, func(i, j int) bool {
x := *topArticlesSlice[i]
y := *topArticlesSlice[j]
// compare numbers of comments
cc := compareIntPtr(x.NumComments, y.NumComments)
// if equal, compare the titles
if cc == 0 {
cc = compareStringPtr(x.Title, y.Title)
}
// return `true` if `x` collates less than `y`, otherwise `false`
return cc < 0
})
return topArticlesSlice
}