내장 타입 정렬하기
slices.Sort는 요소 타입이 순서를 가진(정수, 실수, 문자열) 모든 슬라이스를 정렬합니다. 제자리에서 정렬하고 아무것도 반환하지 않습니다.
출력:
[3 7 19 42 88]
[Bob Carl alice lisa]
true
2 true
문자열은 바이트 기준으로 정렬되므로 모든 ASCII 대문자가 모든 소문자보다 앞에 옵니다. 사람 이름이라면 사용자가 기대하는 결과가 아닌 경우가 대부분입니다. 다음 절에서 이를 고칩니다.
slices 패키지는 Go 1.21에 추가되었습니다. 그 Sort는 pattern-defeating quicksort로, O(n log n)이고 제자리 정렬이며 안정 정렬이 아닙니다.
SortFunc로 원하는 순서 정하기
slices.SortFunc는 비교 함수 func(a, b T) int를 받습니다. a가 앞에 와야 하면 음수, b가 앞에 와야 하면 양수, 같으면 0을 반환하세요. cmp.Compare 헬퍼는 순서를 가진 타입에 대해 정확히 그 값을 반환합니다.
비교 함수 안의 strings.ToLower는 입력에 대문자가 있을 때마다 새 문자열을 할당하고, 비교는 약 n log n번 실행됩니다. 큰 슬라이스라면 소문자 키를 한 번만 계산하세요. 다른 언어의 이름(악센트, 로캘 규칙)에는 표준 라이브러리 밖에 있는 golang.org/x/text/collate를 쓰세요.
비교 함수는 일관되어야 합니다. a가 b보다 앞이라고 했다면 b는 a보다 뒤라고 해야 합니다. 이를 어기는 함수(예: 두 값이 다르면 항상 -1을 반환)는 오류 없이 잘못 정렬된 슬라이스를 만듭니다. 정수 빼기(return a - b)는 깔끔해 보이지만 큰 값에서 오버플로합니다. cmp.Compare를 쓰세요.
구조체 정렬하기
비교 함수는 요소를 받으므로 구조체를 필드로 정렬하는 것도 같은 호출입니다:
SortFunc는 안정 정렬이 아니므로 여기서 급여가 같은 직원들은 어떤 순서로든 나올 수 있습니다. 순서를 보장해야 한다면 필드를 더 비교해서 동점을 가리거나 안정 정렬을 쓰세요.
여러 필드로 정렬하기
가장 중요한 필드를 먼저 비교하고, 동점일 때만 다음 필드로 넘어갑니다. cmp.Or(Go 1.22)는 0이 아닌 첫 번째 인자를 반환하므로 이것이 한 줄이 됩니다:
출력:
eng 150 Cy
eng 120 Ana
eng 120 Eve
sales 90 Bob
sales 90 Dee
비교들은 평범한 인자이므로 첫 번째 비교에서 결정되더라도 모두 평가됩니다. 필드 비교라면 비용이 거의 없습니다. 동점 판정 비용이 크다면 if c := ...; c != 0 { return c } 체인을 직접 쓰세요.
안정 정렬
안정 정렬은 비교 결과가 같은 요소들의 원래 순서를 유지합니다. 입력에 이미 의미 있는 순서가 있을 때 중요합니다. 예를 들어 시간순으로 정렬된 기록을 이제 사용자별로 묶는 경우입니다.
이 코드는 [{ana 2} {ana 4} {bob 1} {bob 3} {bob 5}]를 출력합니다. 사용자마다 원래 순서가 살아남습니다. 안정 정렬은 일을 더 많이 하므로, 같은 요소의 순서가 중요할 때만 쓰세요.
맵 정렬하기
맵에는 순서가 없습니다. 키 순서로 정렬된 맵을 보여 주려면 키를 정렬하세요: slices.Sorted(maps.Keys(m))(Go 1.23). 값으로 정렬하려면 값을 조회하는 비교 함수로 키를 정렬합니다:
단어로 동점을 가리는 부분이 중요합니다. 그게 없으면 키가 맵에서 무작위 순서로 나오기 때문에 chan과 slice(둘 다 7)가 실행할 때마다 다른 순서로 출력됩니다. 자세한 내용은 맵을 참고하세요.
sort 패키지: sort.Slice와 그 친구들
Go 1.21 이전에는 sort 패키지로 정렬했습니다. 기존 코드에서 많이 보게 됩니다:
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 { ... })
알아 둘 차이점:
sort.Slice | slices.SortFunc | |
|---|---|---|
| 함수가 받는 것 | 인덱스 i, j | 요소 a, b |
| 반환 | bool (i가 j보다 작은가) | int (음수, 0, 양수) |
| 타입 안전성 | any를 받고 리플렉션 사용 | 제네릭, 컴파일 타임에 검사 |
| 속도 | 느림 | 빠름 |
sort.Slice의 흔한 버그는 less 함수가 위치로 인덱싱하기 때문에, 정렬하는 슬라이스가 아닌 다른 슬라이스를 캡처하는 것입니다. SortFunc는 요소를 직접 주므로 이런 버그가 생길 수 없습니다.
sort.Interface 타입(Len, Less, Swap)은 가장 오래된 형태입니다. 함께 움직여야 하는 두 병렬 슬라이스처럼 슬라이스 하나가 아닌 데이터를 정렬할 때는 여전히 이 방법을 씁니다. Go 1.22부터 sort.Ints, sort.Strings, sort.Float64s는 그냥 slices.Sort를 호출합니다.
흔한 실수
Sort가 정렬된 슬라이스를 반환하길 기대함. 제자리에서 정렬하고 아무것도 반환하지 않습니다. 새로 정렬된 슬라이스가 필요하면slices.Sorted(slices.Values(s))를 쓰거나 먼저slices.Clone하세요.- 같은 요소가 순서를 유지한다고 가정함. 그것을 보장하는 것은
Stable변형뿐입니다. - 빼기로 비교함.
a - b는 오버플로합니다.cmp.Compare를 쓰세요. - NaN이 섞인 실수를 정렬함.
cmp.Compare는 NaN을 다른 모든 값보다 앞에 두어 정렬을 일관되게 유지합니다. 직접 쓴a < b비교는 그렇지 않습니다.
자주 묻는 질문
Go에서 슬라이스는 어떻게 정렬하나요?
숫자와 문자열이라면 slices.Sort(s)(Go 1.21)를 호출합니다. 제자리에서 오름차순으로 정렬합니다. 그 밖의 타입이나 다른 순서라면 slices.SortFunc(s, func(a, b T) int { ... })를 쓰세요. 함수는 a가 앞이면 음수, b가 앞이면 양수, 같으면 0을 반환합니다.
Go에서 슬라이스를 내림차순으로 정렬하려면 어떻게 하나요?
비교에서 인자의 순서를 바꿉니다: slices.SortFunc(s, func(a, b int) int { return cmp.Compare(b, a) }). 또는 오름차순으로 정렬한 뒤 slices.Reverse(s)를 호출하세요.
Go에서 구조체 슬라이스를 여러 필드로 정렬하려면 어떻게 하나요?
첫 번째 필드를 비교하고, 같을 때만 다음 필드로 넘어갑니다. cmp.Or(Go 1.22)가 정확히 그 일을 합니다: return cmp.Or(cmp.Compare(a.Dept, b.Dept), cmp.Compare(b.Salary, a.Salary), strings.Compare(a.Name, b.Name))는 0이 아닌 첫 번째 결과를 반환합니다.
sort.Slice와 slices.SortFunc의 차이는 무엇인가요?
sort.Slice(s, func(i, j int) bool)는 예전 API로, 인덱스에 대한 less 함수를 받고 리플렉션으로 요소를 교환합니다. slices.SortFunc(s, func(a, b T) int)는 제네릭이라 타입 검사를 받고, 요소를 직접 받으며, 더 빠릅니다. 새 코드는 slices 패키지를 쓰세요. sort.Slice는 Go 1.21 이전에 작성된 코드에서 여전히 흔합니다.