제네릭을 활용한 배열 메서드 구현 연습문제 이해해보기
문제 설명
델리게이트를 활용한 배열 메서드 구현 연습문제에서 만든 MyArray는 int 배열만 처리할 수 있음. 이 메서드들을 제네릭 메서드로 바꿔 어떤 타입의 배열이든 처리하게 하고, 주어진 테스트 코드의 빈칸에 람다식을 채워 정수·문자열·몬스터 배열에 같은 메서드를 적용하는 문제임.
1. 사용할 델리게이트 타입
이전 문제에서 int로 고정했던 타입 인자를 타입 매개변수로 바꿔 사용함.
| 타입 | 매개변수 | 반환 | 용도 |
|---|---|---|---|
Action<T> | T | void | 요소마다 작업 수행 |
Predicate<T> | T | bool | 조건 검사 |
Func<T, T> | T | T | 같은 타입으로 요소 변환 |
Func<TInput, TOutput> | TInput | TOutput | 다른 타입으로 요소 변환 |
Func<T, T, int> | T, T | int | 두 요소 비교 |
2. MyArray 제네릭 메서드 구현
MyArray는 제네릭이 아닌 정적 클래스로 두고, 각 메서드를 제네릭 메서드로 만듦.
ForEach<T>(T[] array, Action<T> action)
- 모든 요소에 대해 앞에서부터 차례로 action을 호출함
Count<T>(T[] array, Predicate<T> match)
- match가 true를 반환하는 요소의 개수를 반환함
Exists<T>(T[] array, Predicate<T> match)
- match를 만족하는 요소가 하나라도 있으면 true를 반환함
FindIndex<T>(T[] array, Predicate<T> match)
- match를 만족하는 첫 번째 요소의 인덱스를 반환함
- 찾지 못하면 -1 반환
Filter<T>(T[] array, Predicate<T> match)
- match를 만족하는 요소만 원래 순서대로 담은 새 T 배열을 반환함
- 원본 배열은 바꾸지 않음
Transform<T>(T[] array, Func<T, T> transformer)
- 각 요소를 transformer의 반환값으로 바꿈 (원본 배열을 직접 수정)
ConvertAll<TInput, TOutput>(TInput[] array, Func<TInput, TOutput> converter)
- 각 요소를 converter로 변환한 값을 같은 순서로 담은 새 TOutput 배열을 반환함
- 예: ["1","2","3"], int.Parse -> [1,2,3]
Sort<T>(T[] array, Func<T, T, int> comparison)
- 버블 정렬로 배열을 정렬함
- comparison(x, y)가 양수이면 x를 y보다 뒤에, 음수이면 앞에 두는 것으로 판단함
- 비교 결과가 0인 두 요소는 원래 순서를 유지함
Sort<T>(T[] array)
- 요소의 기본 순서(작은 수부터, 사전 순)로 정렬함
- T에
where T : IComparable<T>제약을 걸고, 두 요소의 비교는CompareTo로 함 - 8번 Sort를 호출하여 구현함
- T에
3. 테스트 코드 빈칸 채우기
- 아래 테스트 코드의
/* ①~⑯ */자리에 람다식을 써넣어 실행 결과와 같은 출력이 나오게 함 - 빈칸 옆의 주석과 출력 문구를 보고 람다식이 할 일을 판단함
- Monster 클래스는 주어진 그대로 사용함
- 문자열과 이름의 순서 비교는
CompareTo로 함
제약사항
- 컬렉션과
System.Array의 메서드는 사용하지 않음 - MyArray의 메서드 안에서
int,string같은 구체적인 타입을 쓰지 않음 - Monster 클래스와 테스트 코드는 빈칸 외에는 수정하지 않음
제공 코드
class Monster
{
public string Name { get; }
public int Power { get; }
public Monster(string name, int power)
{
Name = name;
Power = power;
}
public override string ToString()
{
return $"{Name}({Power})";
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== 정수 배열 ===");
int[] scores = MyArray.ConvertAll("72 95 48 100 60 91 55".Split(' '), int.Parse);
Console.Write("점수: ");
MyArray.ForEach(scores, /* ① 점수 뒤에 "점 "을 붙여 출력 */);
Console.WriteLine();
Console.WriteLine($"60점 이상 인원: {MyArray.Count(scores, /* ② 60점 이상 */)}명");
int index = MyArray.FindIndex(scores, /* ③ 90점 이상 */);
Console.WriteLine($"첫 90점 이상 위치: {(index == -1 ? "없음" : index.ToString())}");
Console.Write("합격 여부: ");
PrintArray(MyArray.ConvertAll(scores, /* ④ 60점 이상이면 "합격", 아니면 "불합격" */));
MyArray.Sort(scores);
Console.Write("점수 오름차순: ");
PrintArray(scores);
Console.WriteLine();
Console.WriteLine("=== 문자열 배열 ===");
string[] words = { "banana", "kiwi", "apple", "fig", "cherry", "grape" };
Console.Write("단어: ");
PrintArray(words);
Console.Write("5글자 이상 단어: ");
PrintArray(MyArray.Filter(words, /* ⑤ 5글자 이상 */));
Console.WriteLine($"k로 시작하는 단어 존재 여부: {MyArray.Exists(words, /* ⑥ k로 시작 */)}");
Console.Write("단어 길이: ");
PrintArray(MyArray.ConvertAll(words, /* ⑦ 단어의 글자 수 */));
MyArray.Sort(words);
Console.Write("사전 순 정렬: ");
PrintArray(words);
MyArray.Sort(words, /* ⑧ 글자 수가 적은 순 */);
Console.Write("길이 순 정렬: ");
PrintArray(words);
MyArray.Transform(words, /* ⑨ 대문자로 변환 */);
Console.Write("대문자 변환: ");
PrintArray(words);
Console.WriteLine();
Console.WriteLine("=== 몬스터 배열 ===");
Monster[] monsters =
{
new Monster("슬라임", 10),
new Monster("고블린", 30),
new Monster("오크", 55),
new Monster("드래곤", 120),
new Monster("박쥐", 15),
new Monster("트롤", 55),
};
Console.Write("몬스터: ");
PrintArray(monsters);
Console.Write("파워 50 이상: ");
PrintArray(MyArray.Filter(monsters, /* ⑩ 파워 50 이상 */));
Console.WriteLine($"드래곤 위치: {MyArray.FindIndex(monsters, /* ⑪ 이름이 "드래곤" */)}");
Console.Write("이름 목록: ");
PrintArray(MyArray.ConvertAll(monsters, /* ⑫ 몬스터의 이름 */));
MyArray.Sort(monsters, /* ⑬ 이름 오름차순 */);
Console.Write("이름 오름차순: ");
PrintArray(monsters);
MyArray.Sort(monsters, /* ⑭ 이름 내림차순 */);
Console.Write("이름 내림차순: ");
PrintArray(monsters);
MyArray.Sort(monsters, /* ⑮ 파워 오름차순 */);
Console.Write("파워 오름차순: ");
PrintArray(monsters);
MyArray.Sort(monsters, /* ⑯ 파워 내림차순 */);
Console.Write("파워 내림차순: ");
PrintArray(monsters);
}
static void PrintArray<T>(T[] array)
{
Console.Write("[");
for (int i = 0; i < array.Length; i++)
{
Console.Write(array[i]);
if (i < array.Length - 1)
Console.Write(", ");
}
Console.WriteLine("]");
}
}
실행 결과
파워가 같은 오크와 트롤은 정렬 기준이 같으므로 직전 정렬 결과의 순서를 그대로 유지함.
=== 정수 배열 ===
점수: 72점 95점 48점 100점 60점 91점 55점
60점 이상 인원: 5명
첫 90점 이상 위치: 1
합격 여부: [합격, 합격, 불합격, 합격, 합격, 합격, 불합격]
점수 오름차순: [48, 55, 60, 72, 91, 95, 100]
=== 문자열 배열 ===
단어: [banana, kiwi, apple, fig, cherry, grape]
5글자 이상 단어: [banana, apple, cherry, grape]
k로 시작하는 단어 존재 여부: True
단어 길이: [6, 4, 5, 3, 6, 5]
사전 순 정렬: [apple, banana, cherry, fig, grape, kiwi]
길이 순 정렬: [fig, kiwi, apple, grape, banana, cherry]
대문자 변환: [FIG, KIWI, APPLE, GRAPE, BANANA, CHERRY]
=== 몬스터 배열 ===
몬스터: [슬라임(10), 고블린(30), 오크(55), 드래곤(120), 박쥐(15), 트롤(55)]
파워 50 이상: [오크(55), 드래곤(120), 트롤(55)]
드래곤 위치: 3
이름 목록: [슬라임, 고블린, 오크, 드래곤, 박쥐, 트롤]
이름 오름차순: [고블린(30), 드래곤(120), 박쥐(15), 슬라임(10), 오크(55), 트롤(55)]
이름 내림차순: [트롤(55), 오크(55), 슬라임(10), 박쥐(15), 드래곤(120), 고블린(30)]
파워 오름차순: [슬라임(10), 박쥐(15), 고블린(30), 트롤(55), 오크(55), 드래곤(120)]
파워 내림차순: [드래곤(120), 트롤(55), 오크(55), 고블린(30), 박쥐(15), 슬라임(10)]
제네릭 메서드와 델리게이트를 활용한 배열 처리 정리
1. 문제 핵심
MyArray의 메서드를 제네릭 메서드로 구현하여 정수, 문자열, 몬스터처럼 서로 다른 타입의 배열을 같은 방식으로 처리하는 문제다.
- 제네릭 메서드: 타입을 매개변수처럼 받아 여러 자료형을 처리한다.
- 델리게이트: 메서드를 매개변수로 전달할 수 있게 한다.
- 람다식: 전달할 메서드의 동작을 간단하게 표현한다.
- 버블 정렬: 두 요소를 비교하고 순서가 잘못되었으면 교환하여 정렬한다.
2. 델리게이트 핵심 정리
| 델리게이트 | 매개변수 | 반환값 | 용도 |
|---|---|---|---|
Action<T> | T | void | 요소마다 작업 수행 |
Predicate<T> | T | bool | 조건 검사 |
Func<T, T> | T | T | 같은 타입으로 변환 |
Func<TInput, TOutput> | TInput | TOutput | 다른 타입으로 변환 |
Func<T, T, int> | T, T | int | 두 요소 비교 |
람다식 읽는 방법
| 람다식 | 의미 |
|---|---|
x => x >= 60 | x가 60 이상인지 검사한다. |
x => x.Length >= 5 | 글자 수가 5 이상인지 검사한다. |
x => x.Name | 몬스터의 이름을 반환한다. |
x => x.ToUpper() | 문자열을 대문자로 변환한다. |
(x, y) => x.CompareTo(y) | x와 y의 기본 순서를 비교한다. |
(x, y) => y.CompareTo(x) | 기본 순서를 반대로 비교한다. |
3. MyArray 제네릭 메서드 정리
| 메서드 | 기능 | 반환값 | 원본 변경 |
|---|---|---|---|
ForEach<T> | 모든 요소에 작업 수행 | 없음 | 동작에 따라 다름 |
Count<T> | 조건을 만족하는 요소 개수 계산 | int | 변경하지 않음 |
Exists<T> | 조건을 만족하는 요소가 있는지 검사 | bool | 변경하지 않음 |
FindIndex<T> | 조건을 만족하는 첫 번째 요소의 인덱스 검색 | int | 변경하지 않음 |
Filter<T> | 조건을 만족하는 요소만 추출 | T[] | 변경하지 않음 |
Transform<T> | 모든 요소를 변환 | 없음 | 직접 변경 |
ConvertAll<TInput, TOutput> | 모든 요소를 다른 타입으로 변환 | TOutput[] | 변경하지 않음 |
Sort<T> | 지정한 비교 기준으로 정렬 | 없음 | 직접 변경 |
Sort<T> | 기본 순서로 정렬 | 없음 | 직접 변경 |
반드시 구분할 메서드
Filter: 조건에 맞는 요소만 모아 새 배열을 반환한다.Transform: 요소를 변환하여 원본 배열을 직접 수정한다.ConvertAll: 각 요소를 다른 타입으로 변환하여 새 배열을 반환한다.Sort: 배열 내부의 요소 순서를 직접 변경한다.
4. 테스트 코드 빈칸 정답
아래는 ①~⑯에 들어갈 람다식이다.
| 번호 | 람다식 | 설명 |
|---|---|---|
| ① | x => Console.Write($"{x}점 ") | 점수 뒤에 점과 공백을 붙여 출력한다. |
| ② | x => x >= 60 | 60점 이상인지 검사한다. |
| ③ | x => x >= 90 | 90점 이상인지 검사한다. |
| ④ | x => x >= 60 ? "합격" : "불합격" | 점수에 따라 합격 여부를 반환한다. |
| ⑤ | x => x.Length >= 5 | 글자 수가 5 이상인지 검사한다. |
| ⑥ | x => x.StartsWith("k") | k로 시작하는지 검사한다. |
| ⑦ | x => x.Length | 문자열의 글자 수를 반환한다. |
| ⑧ | (x, y) => x.Length.CompareTo(y.Length) | 글자 수가 적은 순으로 비교한다. |
| ⑨ | x => x.ToUpper() | 문자열을 대문자로 변환한다. |
| ⑩ | x => x.Power >= 50 | 파워가 50 이상인지 검사한다. |
| ⑪ | x => x.Name == "드래곤" | 이름이 드래곤인지 검사한다. |
| ⑫ | x => x.Name | 몬스터의 이름을 반환한다. |
| ⑬ | (x, y) => x.Name.CompareTo(y.Name) | 이름 오름차순으로 비교한다. |
| ⑭ | (x, y) => y.Name.CompareTo(x.Name) | 이름 내림차순으로 비교한다. |
| ⑮ | (x, y) => x.Power.CompareTo(y.Power) | 파워 오름차순으로 비교한다. |
| ⑯ | (x, y) => y.Power.CompareTo(x.Power) | 파워 내림차순으로 비교한다. |
주의할 부분
①은 ForEach가 Action<T>를 받으므로 반환값 없이 출력만 수행한다.
④는 조건에 따라 문자열을 반환하므로 ConvertAll의 변환 함수로 사용할 수 있다.
⑧은 글자 수를 비교하므로 x.Length.CompareTo(y.Length)를 사용한다. 글자 수가 같으면 비교 결과가 0이 되어 기존 순서를 유지한다.
⑭와 ⑯은 비교 대상의 순서를 반대로 작성하여 내림차순으로 정렬한다.
5. 정렬 메서드의 핵심 원리
비교 결과의 의미
Func<T, T, int>는 두 요소를 비교한 결과를 정수로 반환한다.
| 비교 결과 | 의미 |
|---|---|
| 음수 | 첫 번째 요소를 앞에 배치한다. |
0 | 두 요소의 순서를 유지한다. |
| 양수 | 첫 번째 요소를 뒤에 배치한다. |
예를 들어 다음과 같이 작성한다.
(x, y) => x.CompareTo(y)
기본 오름차순으로 비교한다.
(x, y) => y.CompareTo(x)
비교 순서를 반대로 하여 내림차순으로 정렬한다.
안정 정렬이란?
비교 결과가 0인 요소들의 기존 순서를 유지하는 정렬 방식이다.
이 문제에서는 몬스터의 파워가 같을 때 직전 정렬 결과의 순서를 유지해야 한다.
따라서 버블 정렬에서는 비교 결과가 양수일 때만 두 요소를 교환해야 한다.
오늘은 저번에 작성했던 델리게이트를 활용한 배열 메서드 구현 연습문제를 제네릭을 활용한 배열 메서드 구현 연습으로 변환해보는 시간을 가졌다.
델리게이트를 처음 작성할 때는 막막했었는데 오늘은 예시가 주어지고 해당 예시를 람다식으로 만들어보다보니 조금씩 이해가 가는 부분이 늘어난 듯 하다.
아직 많이 부족하니까 코드 리뷰를 많이 해보고 직접 작성을 많이 해보는 것이 중요해보인다.



