Combine different sort in Go

huangapple go评论86阅读模式
英文:

Combine different sort in Go

问题

如何在Go中组合排序器?例如,首先按评论数量排序,但如果评论数量为空,则按字母顺序排序。

这是我尝试过的代码:

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
}

我的结构体(https://go.dev/play/p/27j-sFKaG2M)

type ArticleResponse struct {
	Page       int        `json:"page"`
	PerPage    int        `json:"per_page"`
	Total      int        `json:"total"`
	TotalPages int        `json:"total_pages"`
	Articles   []*Article `json:"data"`
}

type Article struct {
	Title       *string     `json:"title"`
	URL         *string     `json:"url"`
	Author      string      `json:"author"`
	NumComments *int        `json:"num_comments"`
	StoryID     interface{} `json:"story_id"`
	StoryTitle  *string     `json:"story_title"`
	StoryURL    *string     `json:"story_url"`
	ParentID    *int        `json:"parent_id"`
	CreatedAt   int         `json:"created_at"`
}
英文:

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
}

My structs (https://go.dev/play/p/27j-sFKaG2M)


type ArticleResponse struct {
	Page       int        `json:"page"`
	PerPage    int        `json:"per_page"`
	Total      int        `json:"total"`
	TotalPages int        `json:"total_pages"`
	Articles   []*Article `json:"data"`
}

type Article struct {
	Title       *string     `json:"title"`
	URL         *string     `json:"url"`
	Author      string      `json:"author"`
	NumComments *int        `json:"num_comments"`
	StoryID     interface{} `json:"story_id"`
	StoryTitle  *string     `json:"story_title"`
	StoryURL    *string     `json:"story_url"`
	ParentID    *int        `json:"parent_id"`
	CreatedAt   int         `json:"created_at"`
}

答案1

得分: 1

你的比较函数太复杂了。你需要将其重构为更简单、更直接的部分。

另外,你还没有定义你的Article类型是什么样子的,所以为了举例,我将定义如下:

type Article struct {
    NumComments *int
    Title       *string
}

你的基本要求是,首先按评论数量排序,然后(如果评论数量为nil)按标题的字母顺序排序,对吗?

从你的原始代码来看,

  • NumComments是一个指向int的指针(*int),
  • Title是一个指向字符串的指针(*string

这意味着每个比较都有四种情况需要处理:

X Y 动作
非nil 非nil 比较x和y(根据它们的底层类型)
非nil nil nil与非nil的比较结果是什么?(实现细节)
nil 非nil nil与非nil的比较结果是什么?(实现细节)
nil nil 两个nil在排序方面是相等的

为了完成这个练习,我将声明nil在与非nil的情况下排序靠后(但nil在与非nil的情况下排序靠前也是同样有效的。这是一种实现选择)。

比较两个*int值很简单:

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
}

比较两个*string值也是一样的:

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
}

一旦你有了这些基本操作,排序函数的比较器函数就更简单了:

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]

        // 比较评论数量
        cc := compareIntPtr(x.NumComments, y.NumComments)

        // 如果相等,比较标题
        if cc == 0 {
            cc = compareStringPtr(x.Title, y.Title)
        }

        // 如果`x`排序小于`y`,返回`true`,否则返回`false`
        return cc < 0
    })

    return topArticlesSlice
}
英文:

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), and
  • Title 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 &amp;&amp; y != nil: cc = sign(*x - *y)
  case x == nil &amp;&amp; y == nil: cc =  0
  case x == nil &amp;&amp; y != nil: cc = +1
  case x != nil &amp;&amp; y == nil: cc = -1
  }
  
  return cc
}

func sign(n int) int {
  var sign int

  switch {
  case n &lt; 0: sign = -1
  case n &gt; 0: sign = +1
  default:    sign =  0
  }

  return sign
}

As is comparing two *string values:

import &quot;strings&quot;
.
.
.
func compareStringPtr(x *string, y *string) int {
  var cc int
  
  switch {
  case x != nil &amp;&amp; y != nil: cc =  strings.Compare(*x, *y)
  case x == nil &amp;&amp; y == nil: cc =  0
  case x == nil &amp;&amp; y != nil: cc = +1
  case x != nil &amp;&amp; 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 &lt; 0
  })
  
  return topArticlesSlice
}

答案2

得分: 0

以下是你提供的代码的翻译:

package main

import (
	"fmt"
	"sort"
)

type Article struct {
	Title       string
	NumComments int
}

func main() {
	a1 := Article{"狗", 3}
	a2 := Article{"老虎", 0}
	a3 := Article{"猫", 4}
	a4 := Article{"鱼", 0}
	a5 := Article{"鲸鱼", 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("文章: %v\n", articles)
}

请注意,我在代码中将动物的名称翻译为中文。如果你有其他问题,请随时提问。

英文:
    package main

import (
	&quot;fmt&quot;
	&quot;sort&quot;
)

type Article struct {
	Title       string
	NumComments int
}

func main() {
	a1 := Article{&quot;Dog&quot;, 3}
	a2 := Article{&quot;Tiger&quot;, 0}
	a3 := Article{&quot;Cat&quot;, 4}
	a4 := Article{&quot;Fish&quot;, 0}
	a5 := Article{&quot;Whale&quot;, 8}

	articles := []Article{a1, a2, a3, a4, a5}

	sort.Slice(articles, func(i, j int) bool {

		if articles[i].NumComments == 0 &amp;&amp; articles[j].NumComments == 0 {
			return articles[i].Title &lt; articles[j].Title
		} else {
			return articles[i].NumComments &lt; articles[j].NumComments
		}

	})

	fmt.Printf(&quot;articles: %v\n&quot;, 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?

答案3

得分: 0

假设我正确理解了你的要求,你可以使用类似以下的代码:

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
}

你可以在playground中尝试这个代码。

英文:

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 &amp;&amp; topArticlesSlice[j].NumComments != nil &amp;&amp;
			*topArticlesSlice[i].NumComments != *topArticlesSlice[j].NumComments {
			return *topArticlesSlice[i].NumComments &lt; *topArticlesSlice[j].NumComments
		}
		if topArticlesSlice[i].Title != nil &amp;&amp; topArticlesSlice[j].Title != nil &amp;&amp;
			*topArticlesSlice[i].Title != *topArticlesSlice[j].Title {
			return *topArticlesSlice[i].Title &lt; *topArticlesSlice[j].Title
		} else if topArticlesSlice[i].StoryTitle != nil &amp;&amp; topArticlesSlice[j].StoryTitle != nil {
			return *topArticlesSlice[i].StoryTitle &lt; *topArticlesSlice[j].StoryTitle
		}
		return false
	})
	return topArticlesSlice
}

Try this in the playground.

huangapple
  • 本文由 发表于 2022年3月5日 03:19:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/71356281.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定