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.Slice | slices.SortFunc | |
|---|---|---|
| A função recebe | índices i, j | elementos a, b |
| Devolve | bool (i é menor que j?) | int (negativo, zero, positivo) |
| Segurança de tipos | recebe any, usa reflection | genérica, verificada em tempo de compilação |
| Velocidade | mais lenta | mais 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
Sortdevolva o slice ordenado. Ele ordena no lugar e não devolve nada. Useslices.Sorted(slices.Values(s))se quiser um slice novo ordenado, ouslices.Cloneantes. - Supor que elementos iguais mantêm a ordem. Só as variantes
Stableprometem isso. - Subtrair para comparar.
a - bestoura. Usecmp.Compare. - Ordenar floats com NaN.
cmp.Comparecoloca NaN antes de todos os outros valores, o que mantém a ordenação consistente. Uma comparaçãoa < bescrita à 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.