- 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
- 책 또는 웹사이트의 내용을 발췌, 요약하여 강의 자료, 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.
정렬 활용하기
이재홍 http://www.pyrasis.com 2014.12.17 ~ 2015.02.07
정렬 키로 정렬하기
이번에는 인터페이스를 교체하는 방법 대신 정렬 키 함수를 사용하는 방법에 대해 알아보겠습니다. 다음은 정렬 키의 기본 형태입니다.
type Data struct { // 정렬할 데이터
}
type By func(s1, s2 *Data) bool // 각 상황별 정렬 함수를 저장할 타입
func (by By) Sort(data []Data) { // By 함수 타입에 메서드를 붙임
sorter := &dataSorter{ // 데이터와 정렬 함수로 sorter 초기화
data: data,
by: by,
}
sort.Sort(sorter) // 정렬
}
type dataSorter struct {
data []Data // 데이터
by func(s1, s2 *Data) bool // 각 상황별 정렬 함수
}
func (s *dataSorter) Len() int { // 데이터 길이를 구하는 메서드
}
func (s *studentSorter) Less(i, j int) bool { // 대소관계를 판단하는 메서드
}
func (s *studentSorter) Swap(i, j int) { // 데이터 위치를 바꾸는 메서드
}
이제 정렬 키를 사용하여 학생의 이름순, 점수순으로 정렬을 해보겠습니다.
package main
import (
"fmt"
"sort"
)
type Student struct {
name string
score float32
}
type By func(s1, s2 *Student) bool // 각 상황별 정렬 함수를 저장할 타입
func (by By) Sort(students []Student) { // By 함수 타입에 메서드를 붙임
sorter := &studentSorter{ // 데이터와 정렬 함수로 sorter 초기화
students: students,
by: by,
}
sort.Sort(sorter) // 정렬
}
type studentSorter struct {
students []Student // 데이터
by func(s1, s2 *Student) bool // 각 상황별 정렬 함수
}
func (s *studentSorter) Len() int {
return len(s.students) // 슬라이스의 길이를 구함
}
func (s *studentSorter) Less(i, j int) bool {
return s.by(&s.students[i], &s.students[j]) // by 함수로 대소관계 판단
}
func (s *studentSorter) Swap(i, j int) {
s.students[i], s.students[j] = s.students[j], s.students[i] // 데이터 위치를 바꿈
}
func main() {
s := []Student{
{"Maria", 89.3},
{"Andrew", 72.6},
{"John", 93.1},
}
name := func(p1, p2 *Student) bool { // 이름 오름차순 정렬 함수
return p1.name < p2.name
}
score := func(p1, p2 *Student) bool { // 점수 오름차순 정렬 함수
return p1.score < p2.score
}
reverseScore := func(p1, p2 *Student) bool { // 점수 내림차순 정렬 함수
return !score(p1, p2)
}
By(name).Sort(s) // name 함수를 사용하여 이름 오름차순 정렬
fmt.Println(s)
By(reverseScore).Sort(s) // reverseScore 함수를 사용하여 점수 내림차순 정렬
fmt.Println(s)
}
[{Andrew 72.6} {John 93.1} {Maria 89.3}]
[{John 93.1} {Maria 89.3} {Andrew 72.6}]
문법이 다소 생소해보이지만 간단한 내용입니다. 먼저 By라는 함수 타입을 만듭니다. By는 *Student 타입의 매개변수 s1, s2를 받고 bool을 리턴하는 함수 타입일 뿐 실제 함수가 아닙니다. 나중에 이름, 점수 순으로 정렬하는 실제 함수를 By 타입으로 변환하게 됩니다.
type By func(s1, s2 *Student) bool // 각 상황별 정렬 함수를 저장할 타입
이제 By 함수 타입에 메서드를 붙입니다. 즉, 함수 타입도 구조체, 슬라이스와 마찬가지로 메서드를 붙일 수 있습니다. 그리고 Sorter 구조체도 정의합니다.
func (by By) Sort(students []Student) { // By 함수 타입에 메서드를 붙임
sorter := &studentSorter{ // 데이터와 정렬 함수로 sorter 초기화
students: students,
by: by,
}
sort.Sort(sorter) // 정렬
}
type studentSorter struct {
students []Student // 데이터
by func(s1, s2 *Student) bool // 각 상황별 정렬 함수
}
- By 타입에 Sort 메서드를 구현합니다. 정렬할 슬라이스와 By 타입의 함수(이름, 점수순 정렬 키 함수)를 studentSorter 구조체에 넣은 뒤 sort.Sort 함수로 정렬합니다.
- studentSorter 구조체는 정렬할 데이터인 Student 슬라이스와 정렬 키 함수를 필드로 가지고 있습니다. 이 슬라이스와 키 함수는 Len, Less, Swap 메서드에서 사용됩니다.
실제 정렬을 수행하는 studentSorter 구조체에 sort.Interface를 구현합니다.
func (s *studentSorter) Len() int {
return len(s.students) // 슬라이스의 길이를 구함
}
func (s *studentSorter) Less(i, j int) bool {
return s.by(&s.students[i], &s.students[j]) // by 함수로 대소관계 판단
}
func (s *studentSorter) Swap(i, j int) {
s.students[i], s.students[j] = s.students[j], s.students[i] // 데이터 위치를 바꿈
}
- Len: 데이터의 개수(길이)를 구합니다. len(s.students)처럼 studentSorter에 저장된 슬라이스의 길이를 구합니다.
- Less: 대소관계를 판단합니다. 두 데이터 i, j를 받은 뒤 i가 작으면
true
를 리턴하도록 구현합니다. 여기서는 부등호로 데이터를 비교하지 않고, studentSorter에 저장된 by(정렬 키) 함수를 사용합니다. - Swap: Less 메서드에서
true
가 나오면 두 데이터의 위치를 바꿉니다.
이제 학생 데이터를 준비하고, 정렬 키 함수를 정의한 뒤 이름 오름차순, 점수 내림차순으로 정렬합니다.
s := []Student{
{"Maria", 89.3},
{"Andrew", 72.6},
{"John", 93.1},
}
name := func(p1, p2 *Student) bool { // 이름 오름차순 정렬 함수
return p1.name < p2.name
}
score := func(p1, p2 *Student) bool { // 점수 오름차순 정렬 함수
return p1.score < p2.score
}
reverseScore := func(p1, p2 *Student) bool { // 점수 내림차순 정렬 함수
return !score(p1, p2)
}
By(name).Sort(s) // name 함수를 사용하여 이름 오름차순 정렬
fmt.Println(s)
By(reverseScore).Sort(s) // reverseScore 함수를 사용하여 점수 내림차순 정렬
fmt.Println(s)
name, score, reverseScore 정렬 키 함수를 정의합니다. name 함수는 이름을 오름차순으로 정렬하고, score 함수는 점수를 오름차순으로 정렬합니다. 이때 reverseScore 함수는 내림차순 정렬을 할 것이므로 score 함수를 호출하여 ! 연산자로 결과를 뒤집어주면 됩니다.
Sort 메서드를 사용할 수 있도록 name, reverseScore 함수를 By 타입으로 변환합니다. 그리고 Sort 함수에 학생 데이터를 넣어서 정렬합니다. 즉 함수의 타입을 변환하여 기능(Sort 메서드)을 사용하고, 내부적으로 함수 자기 자신을 호출하여 정렬을 수행하는 방식입니다.
이처럼 Go 언어는 하나의 데이터 타입을 여러 인터페이스로 바꿔가면서 OOP를 구현합니다.
함수의 타입을 바꾸는 방식을 좀 더 기본적으로 표현하면 다음과 같습니다.
var b By = name
b.Sort(s)
fmt.Println(s)
By 타입으로 변수를 선언한 뒤 name 함수를 대입해도 똑같이 동작합니다. By 타입은 Sort 메서드만 붙어있을 뿐 name 함수와 매개변수, 리턴값 자료형이 같으므로 당연히 대입할 수 있습니다. 또한, By(name)은 int(10.1), float32(10), []byte("Hello")과 같은 문법입니다.
저작권 안내
이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.- 블로그, 게시판 등에 퍼가는 것을 금지합니다.
- 비공개 포스트에 퍼가는 것을 금지합니다.
- 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
- 링크 및 SNS 공유는 허용합니다.