멈추지 않고 끈질기게
[C#] IComparable 인터페이스, Comparison<T> 대리자 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 .Net 5.0 버전을 기준으로 작성되었습니다.
이번 포스팅에서는 정렬 기준을 결정하는 IComparable 인터페이스와 Comparison 대리자에 대해 알아보도록 하겠습니다.
1. IComparable인터페이스
해당 인터페이스는를 상속받는 경우 CompareTo() 함수를 반드시 구현해야 합니다.
class Check : IComparable
{
public int CompareTo(object obj)
{
throw new NotImplementedException();
}
}
(인터페이스 자동 구현 시 모습)
CompareTo()는 해당 타입의 정렬 기준이 되는 함수로, 해당 타입의 배열이나 리스트에서 Sort() 함수 사용 시 매 원소와 그 다음 원소를 CompareTo() 함수를 통해 비교합니다. 반환 값이 0보다 클 경우 두 원소의 위치를 변경하고, 0보다 작을 경우 변경하지 않습니다. 자주 사용하는 int, string 같은 타입들은 이미 IComparable 인터페이스를 상속받아 CompareTo()를 구현한 상태이므로 해당 타입의 배열이나 리스트 생성 시 별도의 구현 없이 Sort() 함수를 사용할 수 있는 것입니다. IComparable 인터페이스를 구현하지 않은 타입의 배열이나 리스트에 별도의 매개변수 없이 Sort() 함수 사용 시, CPU가 정렬 기준을 알 수 없기 때문에 InvalidOperationException이 발생합니다.
이미 CompareTo()를 구현해둔 타입이나 클래스가 아니더라도 IComparable 인터페이스를 상속받아 구현하면 정렬 기능을 사용할 수 있습니다.
public class Student : IComparable
{
public readonly string name;
public readonly int score;
public Student(string name, int score)
{
this.name = name;
this.score = score;
}
public void Print()
{
Console.WriteLine($"이름: {name} / 점수: {score}");
}
public int CompareTo(object obj)
{
Student other = obj as Student;
if (other == null) //Student 타입이 아닐 경우
throw new Exception("Student 타입이 아닙니다.");
//동점이 아니면 내림차순 정렬
if(score != other.score)
return (other.score - score);
//동점자끼리는 이름 기준(오름차순)으로 정렬
return name.CompareTo(other.name);
}
}
static void Main(string[] args)
{
List<Student> list = new List<Student>{
new Student("철수", 90), new Student("짱구", 30),
new Student("유리", 70), new Student("맹구", 50)
};
//정렬 및 출력
list.Sort();
foreach (var item in list)
item.Print();
Console.WriteLine("\n동점자 추가");
//동점자 추가 후 정렬 및 출력
list.Add(new Student("수지", 70));
list.Sort();
foreach (var item in list)
item.Print();
}
Student 클래스를 정의하면서 IComparable 인터페이스를 상속받았습니다. CompareTo() 함수는 현재 원소의 score가 다음 원소보다 작을 경우 양수를 반환하여 점수 기준 내림차순으로 정렬하게끔 만들었으며, 조건문으로 분기를 나누어 동점자일 경우에는 이름을 기준으로 정렬하도록 구현했습니다. 이 경우는 string 타입의 CompareTo()를 사용하여 오름차순(한글의 경우 가나다 순)으로 정렬되도록 했습니다. 이처럼 CompareTo() 함수를 직접 구현할 경우, 정렬 기준을 사용자가 원하는대로 고도화 시킬 수 있습니다.
2. Comparison<T> 대리자
IComparable 인터페이스를 구현하지 않은 클래스에도 Sort() 함수를 사용할 수 있습니다. 바로 Comparison<T> 대리자를 Sort() 함수의 매개변수로 사용하는 것입니다. System 네임스페이스에 정의된 Comparison<T> 대리자는 다음과 같습니다.
public delegate int Comparison<in T>(T x, T y);
같은 타입의 매개변수를 두개 받아 int 값을 반환하는 형태이며, delegate(대리자) 이므로 해당 형식에 부합하는 함수라면 모두 넘겨줄 수 있습니다. Sort()함수는 매개변수 없이 사용할 경우 해당 타입의 CompareTo() 함수를 기준으로 정렬하지만, Comparison<T> 대리자에 부합하는 매개변수(함수)를 넘겨줄 경우 해당 대리자를 기준으로 정렬합니다.
public class Student
{
public readonly string name;
public readonly int score;
public Student(string name, int score)
{
this.name = name;
this.score = score;
}
public void Print()
{
Console.WriteLine($"이름: {name} / 점수: {score}");
}
public static int CompareByName(Student cur, Student next)
{
//이름 기준 내림차순(가나다 역순) 정렬
return next.name.CompareTo(cur.name);
}
}
static void Main(string[] args)
{
List<Student> list = new List<Student>{
new Student("철수", 90), new Student("짱구", 30),
new Student("유리", 70), new Student("맹구", 50)
};
//클래스에 정의된 함수를 대리자로 전달
list.Sort(Student.CompareByName);
foreach (var item in list)
item.Print();
Console.WriteLine();
//람다식을 대리자로 전달
list.Sort((a, b) => { return a.name.CompareTo(b.name); });
foreach (var item in list)
item.Print();
}
Student 클래스는 IComparable 예시 코드와 거의 비슷하나, 해당 인터페이스를 상속하는 대신에 Comparison<T> 대리자를 통해 전달할 수 있는 타입의 비교용 함수를 static으로 작성해두었습니다. 메인 함수에서는 처음에 Sort() 함수에 CompareByName() 함수를 대리자로 전달하여 이름 기준 내림차순(가나다 역순)으로 정렬하였고, 그 뒤에는 람다식을 대리자로 전달하여 이름 기준 오름차순(가나다 순)으로 정렬하였습니다. 물론 CompareTo() 함수와 Comparison<T> 대리자로 전달할 수 있는 비교용 함수를 모두 구현하여, 기본 정렬 기준과 간혹 필요할 수 있는 정렬 기준을 모두 만들어 둘 수도 있습니다.
코딩 테스트나 알고리즘 공부를 하면서 Sort() 함수를 별 생각없이 사용했었는데, int 타입을 내림차순으로 정렬하는 방법을 찾고 나서 Comparison<T> 대리자에 대해 알게되었고, 기본 정렬 기준이 궁금해져 찾다보니 IComparable 인터페이스에 대해서 알게 되었습니다. 이번 포스팅으로 다시 한 번 정리하고 갑니다.
'C#' 카테고리의 다른 글
[C#] Linq 구문 (0) | 2023.02.18 |
---|---|
[C#] 대리자(Delegate) (0) | 2023.02.09 |
[C#] 딕셔너리(Dictionary) (0) | 2023.01.27 |
[C#] 큐(Queue)와 스택(Stack) (0) | 2023.01.26 |
[C#] 리스트(List) (0) | 2023.01.25 |