Menu

Ordenar slices em Golang: slices.Sort, SortFunc e vários campos

Ordene slices em Go com slices.Sort e slices.SortFunc, ordene structs por um ou vários campos com cmp.Compare, mantenha elementos iguais na ordem com uma ordenação estável e entenda código mais antigo com sort.Slice.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Ordenando tipos embutidos

slices.Sort ordena qualquer slice cujo tipo de elemento seja ordenável: inteiros, floats e strings. Ele ordena no lugar e não devolve nada.

Saída:

[3 7 19 42 88]
[Bob Carl alice lisa]
true
2 true

Strings são ordenadas por bytes, então toda letra ASCII maiúscula vem antes de toda minúscula. Raramente é isso que um usuário espera para nomes. A próxima seção resolve isso.

O pacote slices chegou no Go 1.21. O Sort dele é um pattern-defeating quicksort: O(n log n), no lugar e não estável.

Ordem personalizada com SortFunc

slices.SortFunc recebe uma função de comparação func(a, b T) int. Devolva um número negativo quando a deve vir primeiro, um positivo quando b deve vir primeiro, e zero quando forem iguais. A função auxiliar cmp.Compare devolve exatamente isso para tipos ordenáveis.

strings.ToLower dentro de uma comparação aloca uma string nova sempre que a entrada tem uma letra maiúscula, e a comparação executa cerca de n log n vezes. Para slices grandes, calcule as chaves em minúsculas uma vez só. Para nomes em outros idiomas (acentos, regras locais), use golang.org/x/text/collate, que fica fora da biblioteca padrão.

Uma função de comparação precisa ser consistente: se ela diz que a vem antes de b, precisa dizer que b vem depois de a. Uma que quebra isso (por exemplo, devolvendo -1 sempre que dois valores são diferentes) gera um slice ordenado errado, sem nenhum erro. Subtrair inteiros (return a - b) parece elegante, mas estoura com valores grandes; use cmp.Compare.

Ordenando structs

A função de comparação recebe os elementos, então ordenar structs por um campo é a mesma chamada:

Funcionários com salários iguais podem sair em qualquer ordem aqui, porque o SortFunc não é estável. Se você precisa de uma ordem garantida, desempate com mais campos ou use uma ordenação estável.

Ordenando por vários campos

Compare primeiro o campo mais importante e passe para o próximo só em caso de empate. O cmp.Or (Go 1.22) devolve o seu primeiro argumento diferente de zero, o que transforma isso em uma linha:

Saída:

eng     150 Cy
eng     120 Ana
eng     120 Eve
sales    90 Bob
sales    90 Dee

Todas as comparações são avaliadas mesmo quando a primeira decide, já que são argumentos comuns. Isso custa pouco em comparações de campos. Quando um critério de desempate é caro, escreva à mão a cadeia if c := ...; c != 0 { return c }.

Ordenação estável

Uma ordenação estável mantém na ordem original os elementos que se comparam como iguais. Isso importa quando a entrada já tem uma ordem com significado, por exemplo registros ordenados por horário que você agora agrupa por usuário.

Isto imprime [{ana 2} {ana 4} {bob 1} {bob 3} {bob 5}]: dentro de cada usuário, a sequência original sobrevive. A ordenação estável dá mais trabalho, então use-a só quando a ordem dos elementos iguais importar.

Ordenando um map

Maps não têm ordem. Para mostrar um map ordenado pela chave, ordene as chaves: slices.Sorted(maps.Keys(m)) (Go 1.23). Para ordenar pelo valor, ordene as chaves com uma comparação que consulta os valores:

O desempate pela palavra importa: sem ele, chan e slice (os dois com 7) sairiam em uma ordem diferente a cada execução, porque as chaves saem do map em ordem aleatória. Veja maps para mais detalhes.

O pacote sort: sort.Slice e companhia

Antes do Go 1.21, a ordenação passava pelo pacote sort. Você vai vê-lo em muito código existente:

sort.Ints(nums)
sort.Strings(names)
sort.Slice(staff, func(i, j int) bool {
	return staff[i].Salary < staff[j].Salary
})
sort.SliceStable(staff, func(i, j int) bool { ... })

Diferenças que vale a pena conhecer:

sort.Sliceslices.SortFunc
A função recebeíndices i, jelementos a, b
Devolvebool (i é menor que j?)int (negativo, zero, positivo)
Segurança de tiposrecebe any, usa reflectiongenérica, verificada em tempo de compilação
Velocidademais lentamais rápida

Um bug comum com sort.Slice é a closure capturar um slice diferente do que está sendo ordenado, já que a função less indexa por posição. O SortFunc não pode ter esse bug, porque entrega os próprios elementos.

O tipo sort.Interface (Len, Less, Swap) é a forma mais antiga. Ele ainda é o jeito de ordenar dados que não são um único slice, como dois slices paralelos que precisam se mover juntos. Desde o Go 1.22, sort.Ints, sort.Strings e sort.Float64s simplesmente chamam slices.Sort.

Erros comuns

  • Esperar que o Sort devolva o slice ordenado. Ele ordena no lugar e não devolve nada. Use slices.Sorted(slices.Values(s)) se quiser um slice novo ordenado, ou slices.Clone antes.
  • Supor que elementos iguais mantêm a ordem. Só as variantes Stable prometem isso.
  • Subtrair para comparar. a - b estoura. Use cmp.Compare.
  • Ordenar floats com NaN. cmp.Compare coloca NaN antes de todos os outros valores, o que mantém a ordenação consistente. Uma comparação a < b escrita à mão não faz isso.

Perguntas frequentes

Como ordenar um slice em Go?

Para números e strings, chame slices.Sort(s) (Go 1.21). Ele ordena no lugar, em ordem crescente. Para qualquer outra coisa, ou para outra ordem, use slices.SortFunc(s, func(a, b T) int { ... }), em que a função devolve um número negativo se a vem primeiro, positivo se b vem primeiro, e zero se forem iguais.

Como ordenar um slice em ordem decrescente em Go?

Troque os argumentos na comparação: slices.SortFunc(s, func(a, b int) int { return cmp.Compare(b, a) }). Ou ordene em ordem crescente e depois chame slices.Reverse(s).

Como ordenar um slice de structs por vários campos em Go?

Compare o primeiro campo, e passe para o próximo só quando ele for igual. O cmp.Or (Go 1.22) faz exatamente isso: return cmp.Or(cmp.Compare(a.Dept, b.Dept), cmp.Compare(b.Salary, a.Salary), strings.Compare(a.Name, b.Name)) devolve o primeiro resultado diferente de zero.

Qual a diferença entre sort.Slice e slices.SortFunc?

sort.Slice(s, func(i, j int) bool) é a API mais antiga: ela recebe uma função less sobre índices e usa reflection para trocar os elementos. slices.SortFunc(s, func(a, b T) int) é genérica, verificada pelo compilador, recebe os elementos diretamente e é mais rápida. Código novo deve usar o pacote slices; sort.Slice ainda é comum em código escrito antes do Go 1.21.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR