DMarlon Studio

Lección 10 · 10 de 20

sort.Slice y sort.Interface · Go

Ordene structs en Go con sort.Slice, sort.SliceStable y sort.Interface, invierta el orden con sort.Reverse y busque con sort.Search.

Go · sort.Slice y sort.Interface en DMarlon Studio: el código del ejemplo y su salida en la terminal

El paquete sort ordena de dos formas. La clásica es `sort.Interface`: un tipo con tres métodos, Len, Less y Swap. ByName es un []Product con otro nombre y esos tres métodos; sort.Sort(ByName(ps)) convierte el slice (sin copiarlo: comparten los datos) y lo ordena por nombre.

La forma rápida es `sort.Slice`: recibe el slice y una función less(i, j) que dice si el elemento i va antes que el j. El closure usa ps directamente, y así se ordena por precio en una línea, sin declarar ningún tipo.

Con el slice ordenado por precio, `sort.Search` hace una búsqueda binaria: devuelve el primer índice en el que la condición es verdadera, aquí el primer producto de 30 EUR o más, Lamp. Solo funciona si el slice está ordenado por lo mismo que se busca; si ninguno cumple, devuelve len(ps).

sort.Slice no garantiza el orden de los elementos iguales. `sort.SliceStable` sí: al ordenar por categoría, dentro de office y de tech se conserva el orden anterior por precio (Lamp antes que Desk, Cable antes que Mouse). Así se ordena por varios criterios: primero el secundario y luego, estable, el principal.

`sort.Reverse` envuelve cualquier sort.Interface y le da la vuelta a Less: orden alfabético inverso sin escribir otro tipo. El método String de Product (lección del Stringer) hace que %v escriba cada producto como Mouse/tech/25.

Código · main.go

package mainimport (	"fmt"	"sort")type Product struct {	Name, Category string	Price          float64}// ByName implements sort.Interface: Len, Less and Swap.type ByName []Productfunc (p ByName) Len() int           { return len(p) }func (p ByName) Less(i, j int) bool { return p[i].Name < p[j].Name }func (p ByName) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }func (p Product) String() string { return fmt.Sprintf("%s/%s/%.0f", p.Name, p.Category, p.Price) }func main() {	ps := []Product{{"Mouse", "tech", 25}, {"Desk", "office", 180}, {"Lamp", "office", 40}, {"Cable", "tech", 9}}	sort.Sort(ByName(ps))	fmt.Printf("%-12s%v\n", "by name", ps)	sort.Slice(ps, func(i, j int) bool { return ps[i].Price < ps[j].Price })	fmt.Printf("%-12s%v\n", "by price", ps)	i := sort.Search(len(ps), func(i int) bool { return ps[i].Price >= 30 }) // binary search	fmt.Println("first from 30 EUR:", ps[i].Name)	sort.SliceStable(ps, func(i, j int) bool { return ps[i].Category < ps[j].Category })	fmt.Printf("%-12s%v\n", "by category", ps) // stable: keeps the price order inside each category	sort.Sort(sort.Reverse(ByName(ps)))	fmt.Printf("%-12s%v\n", "name desc", ps)}

Salida · go run main.go

by name     [Cable/tech/9 Desk/office/180 Lamp/office/40 Mouse/tech/25]
by price    [Cable/tech/9 Mouse/tech/25 Lamp/office/40 Desk/office/180]
first from 30 EUR: Lamp
by category [Lamp/office/40 Desk/office/180 Cable/tech/9 Mouse/tech/25]
name desc   [Mouse/tech/25 Lamp/office/40 Desk/office/180 Cable/tech/9]

Para recordar

  • sort.Interface pide Len, Less y Swap.
  • sort.Slice ordena con una función less(i, j).
  • sort.SliceStable conserva el orden de los iguales.
  • sort.Reverse invierte cualquier sort.Interface.
  • sort.Search busca en un slice ya ordenado.

Ejercicio

Ordene los productos por categoría y, dentro de cada categoría, por nombre, con una sola llamada a sort.Slice cuyo less compare primero Category y, si coinciden, Name.