HOWTO · Go

Cómo ordenar un slice de structs en Go

Este tutorial demuestra cómo ordenar la porción de estructuras en GoLang.

En esta página

Usa el paquete estándar sort para ordenar un slice de structs. sort.Slice recibe el slice y una función de comparación que acepta dos índices; compara los campos de slice[i] y slice[j]. La función ordena el slice en el mismo lugar: cambiar < por > invierte el orden, de ascendente a descendente.

Los ejemplos muestran el slice resultante después de cada ordenación. Como la operación modifica el slice original, si necesitas conservar el orden de entrada, haz una copia antes. Copiar solo la cabecera del slice no conserva un orden independiente.

Ordenar un slice de structs por un campo con sort.Slice

Este ejemplo completo ordena empleados por salario, de menor a mayor. La función de comparación accede a cada empleado mediante su índice en el slice original.

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(employees)
}
[{John 1500} {Joe 3000} {Jack 3400}]

La comparación con < coloca primero el salario más bajo. Usa > para ordenar los salarios de mayor a menor. sort.Slice modifica el slice proporcionado y no garantiza que los registros con el mismo salario conserven su orden relativo.

Conservar el orden de las claves iguales con sort.SliceStable

sort.SliceStable también ordena structs mediante una función de comparación basada en índices. Su garantía de estabilidad significa que los registros con claves iguales conservan entre sí el orden que tenían en la entrada. Esto resulta útil cuando ese orden tiene significado, por ejemplo, si refleja una clasificación previa o el orden de importación.

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name   string
	Salary int
}

func main() {
	employees := []Employee{
		{Name: "Zoe", Salary: 3000},
		{Name: "John", Salary: 1500},
		{Name: "Ava", Salary: 3000},
	}

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

	fmt.Println(employees)
}
[{John 1500} {Zoe 3000} {Ava 3000}]

La comparación por salario ordena los registros y mantiene a Zoe antes que Ava porque ambas tienen el mismo salario y Zoe aparecía primero. La ordenación estable funciona con structs igual que con otros tipos de elementos de un slice.

Ordenar por varios campos con un criterio de desempate

Para ordenar primero por un campo principal y luego por uno secundario, compara primero los valores principales. Si coinciden, compara los valores secundarios. Este ejemplo ordena los puestos alfabéticamente y, dentro de cada puesto, ordena los nombres también alfabéticamente.

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name     string
	Position string
}

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

	sort.Slice(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(employees)
}
[{Joe CEO} {Michael Developer} {Sheldon Developer} {Leonard Intern} {Jack Manager}]

El comparador debe ser coherente: ordena por Position y consulta Name solo cuando los puestos coinciden. La segunda comparación sirve como criterio de desempate; el orden resultante sigue ambas comparaciones ascendentes. Aquí no hace falta sort.SliceStable, porque el propio comparador define el orden dentro de cada puesto.

Implementar sort.Interface para reutilizar la ordenación

Para definir una regla de ordenación reutilizable, crea un tipo de slice con nombre e implementa sort.Interface. Len indica el número de elementos, Less define su orden y Swap intercambia dos elementos. Después, pasa el slice a sort.Sort.

package main

import (
	"fmt"
	"sort"
)

type Employee struct {
	Name   string
	Salary int
}

type BySalary []Employee

func (employees BySalary) Len() int           { return len(employees) }
func (employees BySalary) Swap(i, j int)      { employees[i], employees[j] = employees[j], employees[i] }
func (employees BySalary) Less(i, j int) bool { return employees[i].Salary < employees[j].Salary }

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

	sort.Sort(employees)
	fmt.Println(employees)
}
[{John 1500} {Joe 3000} {Jack 3400}]

En este caso, BySalary implementa los tres métodos requeridos, así que sort.Sort puede ordenarlo por salario. Este patrón es útil cuando quieres dar un nombre a una misma regla de ordenación y reutilizarla, o cuando la lógica de ordenación pertenece a un tipo de colección personalizado. Para una comparación puntual, normalmente sort.Slice requiere menos código.

Elige sort.Slice cuando el orden sea específico de una operación o dependa del campo que elija quien la invoque. Usa sort.Interface cuando reutilices una regla con nombre en varios lugares o esta forme parte de una abstracción de colección. En ambos casos, decide si los registros con claves iguales pueden cambiar su orden relativo; elige sort.SliceStable cuando sea necesario conservar su secuencia original.