Combine different sort in Go

  • Mar 04, 2022 at 19:19
  • Mar 06, 2022 at 05:16
  • 53 times
Based on the data dump provided by the Stack Exchange Network. Link to the original question.


-1
0

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"`
}
3 Answers


0
    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?

Copied ✓


0

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.

Copied ✓


2

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 && 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
}
Copied ✓