261

I just had a problem where I had an array of structs, e.g.

package main

import "log"

type Planet struct {
    Name       string  `json:"name"`
    Aphelion   float64 `json:"aphelion"`   // in million km
    Perihelion float64 `json:"perihelion"` // in million km
    Axis       int64   `json:"Axis"`       // in km
    Radius     float64 `json:"radius"`
}

func main() {
    var mars = new(Planet)
    mars.Name = "Mars"
    mars.Aphelion = 249.2
    mars.Perihelion = 206.7
    mars.Axis = 227939100
    mars.Radius = 3389.5

    var earth = new(Planet)
    earth.Name = "Earth"
    earth.Aphelion = 151.930
    earth.Perihelion = 147.095
    earth.Axis = 149598261
    earth.Radius = 6371.0

    var venus = new(Planet)
    venus.Name = "Venus"
    venus.Aphelion = 108.939
    venus.Perihelion = 107.477
    venus.Axis = 108208000
    venus.Radius = 6051.8

    planets := [...]Planet{*mars, *venus, *earth}
    log.Println(planets)
}

Lets say you want to sort it by Axis. How do you do that?

(Note: I have seen http://golang.org/pkg/sort/ and it seems to work, but I have to add about 20 lines just for simple sorting by a very simple key. I have a python background where it is as simple as sorted(planets, key=lambda n: n.Axis) - is there something similar simple in Go?)

1
  • Here another third party github.com/patrickmn/sortutil package. It can do normal sorting and also nested sorting. Here I quote from the documentation about the performance: "While sortutil is convenient, it won't beat a dedicated sort. Interface in terms of performance. Implementing sort. Interface for a type ByName which embeds e.g. []MyStruct and doing sort.Sort(ByName{MySlice}) should be considered when high performance is required."
    – Tutompita
    Commented Jun 30, 2016 at 10:44

8 Answers 8

605

As of Go 1.8 you can now use sort.Slice to sort a slice:

sort.Slice(planets, func(i, j int) bool {
  return planets[i].Axis < planets[j].Axis
})

There is normally no reason to use an array instead of a slice, but in your example you are using an array, so you have to overlay it with a slice (add [:]) to make it work with sort.Slice:

sort.Slice(planets[:], func(i, j int) bool {
  return planets[i].Axis < planets[j].Axis
})

The sorting changes the array, so if you really want you can continue to use the array instead of the slice after the sorting.

8
  • sort.Slice is kind of surprising. The less function takes only indices so it has to (in this answer) use a separately-captured planets array. There seems to be nothing enforcing that the sorted slice and the less function are operating on the same data. For this to work, you have to type planets three times (DRY). Commented Oct 7, 2019 at 5:59
  • planets[:] is crucial. But I dont understand why. Works though.
    – STEEL
    Commented Jan 3, 2020 at 10:16
  • @STEEL Usually you should be using a slice instead of an array in the first place. Then you don't need [:].
    – AndreKR
    Commented Jan 3, 2020 at 10:22
  • Worked great!! thank you. What is confusing is what is being set by i and j, and what is the output. sqlrun -e "SELECT FOO,BAR FROM FOOBAR" sqlrun will use the defaults below to select the server, port, username, password, and output type. --add-server Add / configure Server. --add-user Add / configure User. -A, --autocomplete Turn on autocomplete to MySQL client. This is opposite of how mysql client works where -A turns it off,
    – Rich
    Commented Jan 12, 2023 at 13:50
  • 1
    @mrpandey Ah, I see what you mean. Since the comparator does not get the slice passed the best you can do is either have a function that returns the actual comparator function (you'd still have to repeat the name of the slice once) or just put the whole sorting into a function. Remember, function calls are cheap because of inlining.
    – AndreKR
    Commented Sep 9, 2023 at 21:44
100

UPDATE: This answer relates to older versions of go. For Go 1.8 and newer, see the AndreKR's answer above.


If you want something a bit less verbose than the standard library sort package, you could use the third party github.com/bradfitz/slice package. It uses some tricks to generate the Len and Swap methods needed to sort your slice, so you only need to provide a Less method.

With this package, you can perform the sort with:

slice.Sort(planets[:], func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

The planets[:] part is necessary to produce a slice covering your array. If you make planets a slice instead of an array you could skip that part.

4
  • 55
    I have to use third party package to sort an array (unless I want to write incredible amount of verbose code)? What's wrong with this language? I mean... It's just sort! No black magic.
    – Jendas
    Commented Apr 6, 2016 at 7:07
  • 11
    @jendas Go is meant to be simple, not easy. Ruby is easy. Even when not knowing exactly how something works, you can try and it'll work as expected. But don't you dare trying to understand the specs of the language and build an interpreter, or read rails' code while learning ruby. Go is simple. You're advised, after the tour, to read the language spec - even beginners can. And they can read the most advanced code and get it. Because it's simple.
    – kik
    Commented Jul 18, 2016 at 0:50
  • 24
    @kik That does make no sense. Simple does not mean featureless. Sort is one of the most important and fundamental, yet simple features a library could have. Golang has a standard library for html templates, crc32 hashes, printers, scanners etc. That does make it NO LESS simple. Having no sorting in your standard library is not a matter of simplicity, is a matter of missing fundamentals features that all languages consider a standard. Even C has a Sorting function. Stop being so elitists with Golang and start considering that Golang may just was wrong on this one (if it indeed didnt have it).
    – Eksapsy
    Commented Nov 27, 2019 at 21:30
  • 1
    func Slice is added in go1.8
    – Jerry An
    Commented Nov 19, 2021 at 2:28
51

As of Go 1.8, @AndreKR's answer is the better solution.


You can implement a collection type which implements the sort interface.

Here's an example of two such types which allow you to sort either by Axis or Name:

package main

import "log"
import "sort"

// AxisSorter sorts planets by axis.
type AxisSorter []Planet

func (a AxisSorter) Len() int           { return len(a) }
func (a AxisSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a AxisSorter) Less(i, j int) bool { return a[i].Axis < a[j].Axis }

// NameSorter sorts planets by name.
type NameSorter []Planet

func (a NameSorter) Len() int           { return len(a) }
func (a NameSorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a NameSorter) Less(i, j int) bool { return a[i].Name < a[j].Name }

type Planet struct {
    Name       string  `json:"name"`
    Aphelion   float64 `json:"aphelion"`   // in million km
    Perihelion float64 `json:"perihelion"` // in million km
    Axis       int64   `json:"Axis"`       // in km
    Radius     float64 `json:"radius"`
}

func main() {
    var mars Planet
    mars.Name = "Mars"
    mars.Aphelion = 249.2
    mars.Perihelion = 206.7
    mars.Axis = 227939100
    mars.Radius = 3389.5

    var earth Planet
    earth.Name = "Earth"
    earth.Aphelion = 151.930
    earth.Perihelion = 147.095
    earth.Axis = 149598261
    earth.Radius = 6371.0

    var venus Planet
    venus.Name = "Venus"
    venus.Aphelion = 108.939
    venus.Perihelion = 107.477
    venus.Axis = 108208000
    venus.Radius = 6051.8

    planets := []Planet{mars, venus, earth}
    log.Println("unsorted:", planets)

    sort.Sort(AxisSorter(planets))
    log.Println("by axis:", planets)

    sort.Sort(NameSorter(planets))
    log.Println("by name:", planets)
}
2
  • This is exactly the verbose solution I've linked, isn't it? Commented Mar 12, 2015 at 0:39
  • 2
    You linked it while I was writing this. My apologies. But using just the standard tools, there is no shorter way to do this.
    – jimt
    Commented Mar 12, 2015 at 0:40
7

You can, instead of implementing the Sort interface on []Planet you implement on a type that contains the collection and a closure that will do the comparison. You have to provide the implementation for the comparison closure for each property.

This method I feel is better than implementing a Sort type for each property of the struct.

This answer is almost ripped right from the sort docs so I can't take to much credit for it

package main

import (
    "log"
    "sort"
)

type Planet struct {
    Name       string  `json:"name"`
    Aphelion   float64 `json:"aphelion"`   // in million km
    Perihelion float64 `json:"perihelion"` // in million km
    Axis       int64   `json:"Axis"`       // in km
    Radius     float64 `json:"radius"`
}

type By func(p1, p2 *Planet) bool

func (by By) Sort(planets []Planet) {
    ps := &planetSorter{
        planets: planets,
        by:      by, 
    }
    sort.Sort(ps)
}

type planetSorter struct {
    planets []Planet
    by      func(p1, p2 *Planet) bool 
}

func (s *planetSorter) Len() int {
    return len(s.planets)
}

func (s *planetSorter) Swap(i, j int) {
    s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}

func (s *planetSorter) Less(i, j int) bool {
    return s.by(&s.planets[i], &s.planets[j])
}

How to call it.

func main() {
    /* Same code as in the question */

    planets := []Planet{*mars, *venus, *earth}

    By(func(p1, p2 *Planet) bool {
        return p1.Name < p2.Name
    }).Sort(planets)

    log.Println(planets)

    By(func(p1, p2 *Planet) bool {
        return p1.Axis < p2.Axis
    }).Sort(planets)

    log.Println(planets)
}

Here is a Demo

4

Here is another way to reduce some of the boiler plate. Disclaimer, it uses reflection and losses type safety.

Here is a Demo

All the magic happens in the Prop function. It takes the struct property to sort on and the order it which you want to sort (ascending, descending) and returns a function that will perform the comparisons.

package main

import (
    "log"
    "reflect"
    "sort"
)

func test(planets []Planet) {
    log.Println("Sort Name")
    By(Prop("Name", true)).Sort(planets)
    log.Println(planets)

    log.Println("Sort Aphelion")
    By(Prop("Aphelion", true)).Sort(planets)
    log.Println(planets)

    log.Println("Sort Perihelion")
    By(Prop("Perihelion", true)).Sort(planets)
    log.Println(planets)

    log.Println("Sort Axis")
    By(Prop("Axis", true)).Sort(planets)
    log.Println(planets)

    log.Println("Sort Radius")
    By(Prop("Radius", true)).Sort(planets)
    log.Println(planets)
}

func Prop(field string, asc bool) func(p1, p2 *Planet) bool {
    return func(p1, p2 *Planet) bool {

        v1 := reflect.Indirect(reflect.ValueOf(p1)).FieldByName(field)
        v2 := reflect.Indirect(reflect.ValueOf(p2)).FieldByName(field)

        ret := false

        switch v1.Kind() {
        case reflect.Int64:
            ret = int64(v1.Int()) < int64(v2.Int())
        case reflect.Float64:
            ret = float64(v1.Float()) < float64(v2.Float())
        case reflect.String:
            ret = string(v1.String()) < string(v2.String())
        }

        if asc {
            return ret
        }
        return !ret
    }
}

type Planet struct {
    Name       string  `json:"name"`
    Aphelion   float64 `json:"aphelion"`   // in million km
    Perihelion float64 `json:"perihelion"` // in million km
    Axis       int64   `json:"Axis"`       // in km
    Radius     float64 `json:"radius"`
}

type By func(p1, p2 *Planet) bool

func (by By) Sort(planets []Planet) {
    ps := &planetSorter{
        planets: planets,
        by:      by, // The Sort method's receiver is the function (closure) that defines the sort order.
    }
    sort.Sort(ps)
}

type planetSorter struct {
    planets []Planet
    by      func(p1, p2 *Planet) bool // Closure used in the Less method.
}

// Len is part of sort.Interface.
func (s *planetSorter) Len() int { return len(s.planets) }

// Swap is part of sort.Interface.
func (s *planetSorter) Swap(i, j int) {
    s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}

// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *planetSorter) Less(i, j int) bool {
    return s.by(&s.planets[i], &s.planets[j])
}

func main() {
    test(dataSet())
}

func dataSet() []Planet {

    var mars = new(Planet)
    mars.Name = "Mars"
    mars.Aphelion = 249.2
    mars.Perihelion = 206.7
    mars.Axis = 227939100
    mars.Radius = 3389.5

    var earth = new(Planet)
    earth.Name = "Earth"
    earth.Aphelion = 151.930
    earth.Perihelion = 147.095
    earth.Axis = 149598261
    earth.Radius = 6371.0

    var venus = new(Planet)
    venus.Name = "Venus"
    venus.Aphelion = 108.939
    venus.Perihelion = 107.477
    venus.Axis = 108208000
    venus.Radius = 6051.8

    return []Planet{*mars, *venus, *earth}
}
0
3

From docs

Note: in many situations, the newer slices.SortFunc function is more ergonomic and runs faster.

Here's an alternative using slices package:

people := []Person{
    Person{name: "Jax", age: 37},
    Person{name: "TJ", age: 25},
    Person{name: "Alex", age: 72},
}

slices.SortFunc(people, func(a, b Person) int {
    return cmp.Compare(a.age, b.age)
})

fmt.Println(people)

for more information please see docs or this example.

1

The other answers are already pretty good.

I want to add this example for sorting alphabetically by a structs Value that is a string. Pretty much the same in Go, might be helpful to someone with other programming language background :)

type My struct {
    Val string
}

func main() {
    m1 := My{Val: "B"}
    m2 := My{Val: "C"}
    m3 := My{Val: "A"}
    m4 := My{Val: "D"}
    mList := []My{m1, m2, m3, m4}
    sort.Slice(mList, func(i, j int) bool {
        return mList[i].Val < mList[j].Val
    })
    fmt.Println("Sorted:", mList[0], mList[1], mList[2], mList[3])
}
0

You can implement using quick sort as well and inside the partition func, you choose which field to sort by, I choose Name for example.

package main

import (
    "fmt"
)

type Planet struct {
    Name       string  `json:"name"`
    Aphelion   float64 `json:"aphelion"`   // in million km
    Perihelion float64 `json:"perihelion"` // in million km
    Axis       int64   `json:"Axis"`       // in km
    Radius     float64 `json:"radius"`
}

func main() {
    var mars Planet
    mars.Name = "Mars"
    mars.Aphelion = 249.2
    mars.Perihelion = 206.7
    mars.Axis = 227939100
    mars.Radius = 3389.5

    var earth Planet
    earth.Name = "Earth"
    earth.Aphelion = 151.930
    earth.Perihelion = 147.095
    earth.Axis = 149598261
    earth.Radius = 6371.0

    var venus Planet
    venus.Name = "Venus"
    venus.Aphelion = 108.939
    venus.Perihelion = 107.477
    venus.Axis = 108208000
    venus.Radius = 6051.8

    planets := []Planet{mars, venus, earth}
    fmt.Println(quickSort(&planets,0,len(planets)-1))

}

func quickSort(arr *[]Planet, start, end int)[]Planet{
    if start < end{
        partitionIndex := partition(*arr,start,end)
        quickSort(arr,start,partitionIndex-1)
        quickSort(arr,partitionIndex+1, end)
    }
    return *arr
}

func partition(arr []Planet, start, end int) int{
    pivot := arr[end].Name
    pIndex := start
    for i:= start; i<end; i++{
        if arr[i].Name <= pivot{
            //  swap
            arr[i],arr[pIndex] = arr[pIndex],arr[i]
            pIndex++
        }
    }
    arr[pIndex],arr[end] = arr[end],arr[pIndex]
    return pIndex
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.