[TIL] C# 공부 - 컬렉션, 델레게이트, 람다
📕학습 개요
유니티 캠프 7일차.
이번 주차는 팀 프로젝트가 아닌 개인 프로젝트를 진행하다 보니 조원들 서로 마이크 끄고 서로 할 일만 하는 느낌이다. 조용한 것도 좋지만 저번주에 비해 시간도 느리게 가고 딴 생각도 많이 하게 되는 것 같다. 아침 10시쯤에는 너무 졸려서 눈이 감기는 걸 참느라 애먹었다.
오늘은 오전 시간에 강의를 듣고, 오후 시간에는 프로젝트인 TRPG를 만들기 시작했다. 매 강의 주차에 달려있는 숙제의 난이도가 생각보다 어렵다고 느껴졌는데, 5주차에는 그 정점을 찍었다. 3가지의 알고리즘 문제가 구비되어 있는데, 나한텐 너무 어려워서 하루에 하나씩만 풀어볼려고 한다.
📖학습 내용
1. Largest Rectangle in Histogram
말 그대로 히스토그램에서 가장 넓은 직사각형을 찾는 문제이다.

어떻게 풀어야 할지 30분동안 고민하다, 백준 문제에 적혀 있는 알고리즘 분류에 스택이 적혀있는 걸 보고 힌트를 얻었다. 스택을 어떻게 활용해야 좋을지 또 30분을 고민한 끝에, 직사각형을 차례대로 순회하며, 높이가 이전 막대(스택의 맨 위) 보다 낮으면 그 순간 스택에서 꺼내 “그 높이로 만들 수 있는 직사각형 넓이”를 계산하면 되지 않을까 라는 생각이 들었다.
internal class Program
{
static void Main(string[] args)
{
string[] input = Console.ReadLine().Split();
while (input[0] != "0")
{
long[] heights = new long[input.Length - 1];
for (int i = 0; i < heights.Length; i++)
{
heights[i] = long.Parse(input[i + 1]);
}
Console.WriteLine(LargestRectangleArea(heights));
input = Console.ReadLine().Split();
}
}
static long LargestRectangleArea(long[] heights)
{
Stack<long> stack = new Stack<long>(); //막대의 인덱스를 저장
long maxArea = 0;
long index = 0;
while(index < heights.Length)
{
//높이가 같거나 큰 경우
if(stack.Count == 0 ||
heights[index] >= heights[stack.Peek()])
{
stack.Push(index);
index++;
}
//높이가 작은 경우
else
{
long top = stack.Pop();
//왼쪽 경계가 있다면 너비는 index
//왼쪽 경계가 있으면 (현재 인덱스 - 스택 top - 1)
long width = stack.Count == 0 ? index : index - stack.Peek() - 1;
long area = heights[top] * width;
if(area > maxArea)
{
maxArea = area;
}
}
}
//나머지 막대들도 계산
while(stack.Count > 0)
{
long top = stack.Pop();
long width = stack.Count == 0 ? index : index - stack.Peek() - 1;
long area = heights[top] * width;
if (area > maxArea)
{
maxArea = area;
}
}
return maxArea;
}
}
백준에서 알고리즘 문제를 나름 풀어본 적은 있지만, 항상 기본적인 컬렉션과 정렬 방식을 사용하여 더럽고 시간 복잡도가 높은 풀이만 내놓았던 기억만 있다. 그동안 얼마나 무식하게 문제를 풀었던 것인지 이제서야 깨닳게 되었다. 역시 앞으로 알고리즘 공부를 제대로 해봐야겠다.
2. 컬렉션
-
컬렉션은 배열 처럼 특정 자료를 모아 놓은 데이터 구조를 의미하며, 크기가 가변적이기 때문에 경우에 따라 훨씬 유용하게 사용할 수 있다. 각 상황에 맞게 적절한 컬렉션을 사용하는 것이 매우 중요하다.
-
List
- 가변적인 크기를 갖는 배열
- 제너릭 형태로 자료형을 지정
List<int> numbers = new List<int>(); // 빈 리스트 생성
numbers.Add(1); // 리스트에 데이터 추가
numbers.Add(2);
numbers.Add(3);
numbers.Remove(2); // 리스트에서 데이터 삭제
언뜻 보면 리스트는 배열의 상위호환처럼 보이지만, 메모리 사용량과 데이터 접근 시간이 증가하고, 코드 복잡도를 증가시킬 위험이 있기에 무분별하게 사용하는 것은 좋지 않다.
- Dictionary
- 키와 값으로 구성된 데이터를 저장
- 중복된 키를 가질 수 없으며, 키와 값의 쌍을 이루어 데이터 저장
Dictionary<string, int> scores = new Dictionary<string, int>(); // 빈 딕셔너리 생성
scores.Add("Alice", 100); // 딕셔너리에 데이터 추가
scores.Add("Bob", 80);
scores.Add("Charlie", 90);
scores.Remove("Bob"); // 딕셔너리에서 데이터 삭제
foreach(KeyValuePair<string, int> pair in scores) // 딕셔너리 데이터 출력
{
Console.WriteLine(pair.Key + ": " + pair.Value);
}
- Stack
- 후입선출(FILO) 구조를 가진 자료구조
Stack<int> stack1 = new Stack<int>(); // int형 Stack 선언
// Stack에 요소 추가
stack1.Push(1);
stack1.Push(2);
stack1.Push(3);
// Stack에서 요소 가져오기
int value = stack1.Pop(); // value = 3 (마지막에 추가된 요소)
- Queue
- 선입선출(FIFO) 구조를 가진 자료구조
Queue<int> queue1 = new Queue<int>(); // int형 Queue 선언
// Queue에 요소 추가
queue1.Enqueue(1);
queue1.Enqueue(2);
queue1.Enqueue(3);
// Queue에서 요소 가져오기
int value = queue1.Dequeue(); // value = 1 (가장 먼저 추가된 요소)
- HashSet
- 중복되지 않은 요소들로 이루어진 집합
HashSet<int> set1 = new HashSet<int>(); // int형 HashSet 선언
// HashSet에 요소 추가
set1.Add(1);
set1.Add(2);
set1.Add(3);
// HashSet에서 요소 가져오기
foreach (int element in set1)
{
Console.WriteLine(element);
}
3. 델리게이트, 람다
델리게이트와 람다는 메서드를 변수처럼 사용할 수 있게 해주는 도구라고 생각하면 된다.
delegate- 메서드를 참조하는 포인터 같은 개념
// delegate 정의
delegate int Calculator(int a, int b);
// delegate를 사용하는 메서드
int Add(int a, int b) => a + b;
// delegate 변수에 메서드 할당
Calculator calc = Add;
int result = calc(3, 4); // 결과: 7
위 코드에서는 안나왔지만 하나 이상의 메서드를 등록하는 것도 당연히 가능하다. 이벤트 처리에 굉장히 효과적인 방법인 것 같다.
-
‘lambda`
- 메서드를 간단하게 표현하기 위해 익명 메서드를 사용
delegate를 간단하게 쓰기 위해 등장한 문법
// 기본 람다식: (입력) => 결과
Func<int, int, int> calc = (a, b) => a + b;
int result = calc(3, 4); // 결과: 7
Func 와 Action
Func<>- 리턴값이 있는 메서드를 담기 위한 미리 정의된
delegate타입
- 리턴값이 있는 메서드를 담기 위한 미리 정의된
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // 7
Action<>- 리턴값이 없는 메서드를 담기 위한
delegate타입
- 리턴값이 없는 메서드를 담기 위한
Action<string> print = msg => Console.WriteLine($"[Action] {msg}");
print("Hello!"); // [Action] Hello!
🏁오늘 배운 핵심 한줄 요약
- 항상 상황에 맞는 컬렉션이 뭘까 생각해보자
- 델리게이트, 람다를 통해 메서드를 변수처럼 사용
- Func<> 를 통해 리턴값이 있는 메서드도 델리게이트로 관리 가능
P.S 벌써 4월 15일인데 날씨가 너무 추운 것 같다… 아침 온도 2도가 말이되나… 빨리 반팔의 계절이 왔으면 좋겠다.
댓글남기기