How to Sort Slice of Structs in Go

Sheeraz Gul Feb 12, 2024
  1. Sort Slice of Structs by a Field in GoLang Using the sort.Slice Function
  2. Sort Slice of Structs by a Field in GoLang Using the sort.SliceStable Method
  3. Sort Slice of Structs by Multiple Fields in GoLang Using the sort.SliceStable Method
  4. Sort Slice of Structs in GoLang Using the sort.Interface Methods
  5. Conclusion
How to Sort Slice of Structs in Go

Sorting slices of structs is a common task in GoLang, and the language provides several methods to achieve this, each with its advantages and use cases. In this article, we will explore three prominent approaches for sorting slices of structs: using sort.Slice, sort.SliceStable, and sort.Interface methods.

Sort Slice of Structs by a Field in GoLang Using the sort.Slice Function

The sort package provides a convenient method for sorting a slice of structs in GoLang based on a specific field - the sort.Slice function. Along with sort.Slice, a custom less function can be used to define the sorting logic.

The sort.Slice function provides a convenient way to sort slices of elements using a custom less function. The primary advantage of using sort.Slice is its flexibility, as it allows you to sort slices of any type, not just slices of basic data types.

It has the following syntax:

func Slice(slice interface{}, less func(i, j int) bool)

Where:

  • slice: The slice to be sorted. It should be a slice of a type for which the less function is defined.
  • less: A function that takes two indices, i and j, and returns a Boolean indicating whether the element at index i should be considered less than the element at index j. This function defines the sorting order.

Let’s consider an example where we have a slice of Employee structs, and we want to sort them based on the Salary field. Here’s the complete working code:

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name   string
	Salary int
}

func main() {
	employees := []Employee{
		{Name: "John", Salary: 1500},
		{Name: "Joe", Salary: 3000},
		{Name: "Jack", Salary: 3400},
	}

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

	fmt.Println("Sorted by Salary (Ascending):", employees)
}

In this example, we declare a custom struct named Employee, which has two fields - Name of type string and Salary of type integer. This struct represents the individual employees with their names and corresponding salaries.

Moving on to the main function, we initialize a slice of Employee structs named employees. This slice is a collection of three Employee instances, each initialized with a name and a salary. This forms the dataset that we want to sort.

The key part of the code is the use of sort.Slice. This function takes two arguments - the slice to be sorted (employees in this case) and a custom less function.

The less function defines the sorting order based on the Salary field. It takes two indices, i and j, representing two elements in the slice. The function compares the salaries of the employees at these indices and returns true if the salary at index i is less than the salary at index j. This logic dictates the sorting order.

After applying the sort.Slice method, the employees slice is now sorted in ascending order based on the Salary field. Finally, we use fmt.Println to display the sorted slice, indicating that it is sorted by salary in ascending order.

Output:

Sorted by Salary (Ascending): [{John 1500} {Joe 3000} {Jack 3400}]

This indicates that the slice of Employee structs has been successfully sorted in ascending order based on the Salary field.

Sort Slice of Structs by a Field in GoLang Using the sort.SliceStable Method

An alternative method for sorting a slice of structs in GoLang is the sort.SliceStable method, along with a custom less function.

Similar to the previous example, sort.SliceStable also takes a slice (x) and a custom less function. It has the following syntax:

func SliceStable(slice interface{}, less func(i, j int) bool)

Where:

  • slice: This parameter is the slice to be sorted. It should be an interface value that represents a slice.
  • less: This is a function that takes two indices, i and j, and returns a Boolean indicating whether the element at index i should be considered less than the element at index j. This function defines the custom sorting order.

The key distinction is that sort.SliceStable guarantees a stable sort, meaning that when two elements have equal sorting keys, their original order in the slice is preserved. It is particularly useful when you want to achieve a stable sort, ensuring that the relative order of elements with equal keys remains unchanged after sorting.

Consider the same scenario where we have a slice of Employee structs, and we want to sort them based on the Salary field. Here’s the complete working code:

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name   string
	Salary int
}

func main() {
	employees := []Employee{
		{Name: "John", Salary: 1500},
		{Name: "Joe", Salary: 3000},
		{Name: "Jack", Salary: 3400},
	}

	sort.SliceStable(employees, func(i, j int) bool {
		return employees[i].Salary < employees[j].Salary
	})

	fmt.Println("Sorted by Salary (Stable):", employees)
}

This code largely resembles the previous example, with the primary distinction being the use of sort.SliceStable instead of sort.Slice. We continue to define the Employee struct and initialize a slice of Employee structs named employees.

The sort.SliceStable method is applied with the same custom less function, which compares the Salary values of two employees at indices i and j.

If the salary at index i is less than the salary at index j, the function returns true, indicating that i should come before j in the sorted order. The stability of the sort is crucial; it ensures that when two employees have equal salaries, their original order in the slice is preserved.

As before, we utilize fmt.Println to display the sorted slice, indicating that it is sorted by salary in a stable manner.

Output:

Sorted by Salary (Stable): [{John 1500} {Joe 3000} {Jack 3400}]

This output affirms that the slice of Employee structs has been successfully sorted in ascending order based on the Salary field while maintaining stability in the relative order of equal salaries.

Sort Slice of Structs by Multiple Fields in GoLang Using the sort.SliceStable Method

Expanding on our exploration of sorting slices of structs in GoLang, we will now focus on sorting by multiple fields. This scenario often arises when you need to prioritize sorting based on one field and then, if there are ties, sort by another field.

In this section, we’ll utilize the sort.SliceStable method with a custom less function to achieve this multi-field sorting.

The process is analogous to sorting by a single field, but the custom less function becomes more intricate. The function needs to compare multiple fields sequentially.

If the values of the first field are equal, it proceeds to compare the values of the second field, and so on. This ensures a stable sort by maintaining the order of equal elements based on the previous fields.

Let’s consider a scenario where we have a slice of Employee structs, and we want to sort them first by Position and then by Name. Here’s the complete working code:

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name     string
	Position string
}

func main() {
	employees := []Employee{
		{"Michael", "Developer"},
		{"Jack", "Manager"},
		{"Joe", "CEO"},
		{"Leonard", "Intern"},
		{"Sheldon", "Developer"},
	}

	sort.SliceStable(employees, func(i, j int) bool {
		if employees[i].Position != employees[j].Position {
			return employees[i].Position < employees[j].Position
		}
		return employees[i].Name < employees[j].Name
	})

	fmt.Println("Sorted by Position and then by Name (Stable):", employees)
}

In this code, the central data structure is the Employee struct, characterized by two fields: Name (a string representing the employee’s name) and Position (a string indicating the employee’s position within the company).

The main function initializes a slice of Employee structs named employees. This slice serves as the dataset that we aim to sort based on the specified fields.

The core logic revolves around the use of the sort.SliceStable method, which ensures a stable sort. A custom less function is provided as an argument to this method, dictating the sorting order.

This particular less function compares the Position values of two employees. If the positions are different, it returns true if the position at index i is less than the position at index j. If the positions are equal, it proceeds to compare the Name values, ensuring a stable sort by maintaining the order of equal positions based on the names.

Finally, the sorted slice is displayed using fmt.Println.

This multi-field sorting provides a practical example of how to prioritize sorting based on one field and then, in the case of ties, sorting by another field.

Output:

Sorted by Position and then by Name (Stable): [{Joe CEO} {Michael Developer} {Sheldon Developer} {Leonard Intern} {Jack Manager}]

This output illustrates that the slice of Employee structs has been successfully sorted first by Position and then, for employees with the same position, by Name. Understanding this approach allows you to effectively handle more complex sorting requirements in GoLang.

Sort Slice of Structs in GoLang Using the sort.Interface Methods

The sort package also provides a powerful mechanism for sorting slices of structs using the sort.Interface interface. By implementing specific methods from this interface, we gain fine-grained control over the sorting process.

This approach offers flexibility and allows us to define custom sorting logic tailored to our struct’s requirements.

The sort.Interface interface in GoLang requires the implementation of three methods: Len() int, Less(i, j int) bool, and Swap(i, j int).

Here is the syntax for each of these methods:

func (x YourType) Len() int {
    // Return the length of the slice or collection.
}

The Len method should return the number of elements in the collection.

func (x YourType) Less(i, j int) bool {
    // Return true if the element at index i should be considered less than the element at index j.
}

The Less method should define the custom comparison logic. It returns true if the element at index i should be considered less than the element at index j; otherwise, it returns false.

func (x YourType) Swap(i, j int) {
    // Swap the elements at indices i and j.
}

The Swap method should exchange the elements at indices i and j in the collection.

Let’s delve into a practical example by sorting a slice of Employee structs based on the Salary field:

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name   string
	Salary int
}

type EmployeeSlice []Employee

func (e EmployeeSlice) Len() int {
	return len(e)
}

func (e EmployeeSlice) Swap(i, j int) {
	e[i], e[j] = e[j], e[i]
}

func (e EmployeeSlice) Less(i, j int) bool {
	return e[i].Salary < e[j].Salary
}

func main() {
	employees := EmployeeSlice{
		{Name: "John", Salary: 1500},
		{Name: "Joe", Salary: 3000},
		{Name: "Jack", Salary: 3400},
	}

	sort.Sort(employees)

	fmt.Println("Sorted by Salary:", employees)
}

In this example, we define a custom type, EmployeeSlice, which is a slice of Employee structs. We then implement the required methods for the sort.Interface interface on this custom type.

The Len() method returns the length of the slice, Swap(i, j int) swaps elements at indices i and j, and Less(i, j int) bool compares the Salary values of the Employee structs at indices i and j.

Moving on to the main function, a slice of Employee structs named employees is initialized with a dataset representing employees and their respective salaries. The sort.Sort function is then employed, taking advantage of the sort.Interface methods implemented for the custom EmployeeSlice type.

This initiates the sorting process, arranging the Employee structs in ascending order based on their Salary field. The result is a sorted slice of Employee structs based on the Salary field.

Output:

Sorted by Salary: [{John 1500} {Joe 3000} {Jack 3400}]

This output confirms that the slice of Employee structs has been successfully sorted based on the Salary field using the sort.Interface methods. This approach provides a comprehensive way to handle custom sorting logic for slices of structs in GoLang programs.

Conclusion

Sorting slices of structs in GoLang can be accomplished using different methods, each suited to specific scenarios.

Whether you need a simple sort with sort.Slice, stable sorting with sort.SliceStable, or a more customized approach with sort.Interface, GoLang provides the tools to efficiently manage and organize your data. Understanding these methods empowers you to choose the most suitable approach for your sorting requirements.

Author: Sheeraz Gul
Sheeraz Gul avatar Sheeraz Gul avatar

Sheeraz is a Doctorate fellow in Computer Science at Northwestern Polytechnical University, Xian, China. He has 7 years of Software Development experience in AI, Web, Database, and Desktop technologies. He writes tutorials in Java, PHP, Python, GoLang, R, etc., to help beginners learn the field of Computer Science.

LinkedIn Facebook

Related Article - Go Slice

Related Article - Go Struct