정렬 활용하기


이재홍 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 공유는 허용합니다.

Published

01 June 2015