Menu

Golang 슬라이스 정렬: slices.Sort, SortFunc, 여러 필드 정렬

slices.Sort와 slices.SortFunc로 Go 슬라이스를 정렬하고, cmp.Compare로 구조체를 필드 하나나 여러 개로 정렬하며, 안정 정렬로 같은 요소의 순서를 유지하고, 예전의 sort.Slice 코드를 읽는 법을 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

내장 타입 정렬하기

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를 쓰세요.

비교 함수는 일관되어야 합니다. ab보다 앞이라고 했다면 ba보다 뒤라고 해야 합니다. 이를 어기는 함수(예: 두 값이 다르면 항상 -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). 값으로 정렬하려면 값을 조회하는 비교 함수로 키를 정렬합니다:

단어로 동점을 가리는 부분이 중요합니다. 그게 없으면 키가 맵에서 무작위 순서로 나오기 때문에 chanslice(둘 다 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.Sliceslices.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 이전에 작성된 코드에서 여전히 흔합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기