
지난 글에서 우리는 순서가 있는 데이터 묶음의 표준, List<T>에 대해 알아봤습니다.
List<T>는 정말 다재다능해서 대부분의 상황에서 유용하게 쓰이죠.
때로는 데이터에 '규칙'을 부여하고 싶을 때가 있습니다.
이번 글에서는 데이터의 저장 순서나 접근 방식이 특별한 제네릭 컬렉션을 살펴보겠습니다.
큐(Queue)는 이름 그대로 '대기열'을 흉내 낸 컬렉션입니다.
먼저 들어간 데이터가 먼저 나온다는 선입선출(FIFO: First-In, First-Out) 원칙을 따릅니다.
가장 먼저 줄을 선 사람이 가장 먼저 표를 삽니다.
중간에 끼어들거나 뒤에 있는 사람이 먼저 처리될 수 없죠.
Enqueue(item): 큐의 맨 뒤에 데이터를 추가합니다. (줄의 맨 끝에 서기)Dequeue(): 큐의 맨 앞에서 데이터를 꺼내고 제거합니다.Peek(): 큐의 맨 앞에 있는 데이터를 제거하지 않고 확인만 합니다.[코드]
using System;
using System.Collections.Generic; // Queue<T>를 사용하려면 필요합니다.
// 상황: 은행 창구 대기열
Queue<string> waitingQueue = new Queue<string>();
// 1. 대기열에 손님들이 도착 (Enqueue)
waitingQueue.Enqueue("박철수");
waitingQueue.Enqueue("이민수");
waitingQueue.Enqueue("김유리");
Console.WriteLine($"다음 손님은 '{waitingQueue.Peek()}' 입니다.");
// 2. 창구에서 손님 처리 (Dequeue)
while (waitingQueue.Count > 0)
{
string currentCustomer = waitingQueue.Dequeue();
Console.WriteLine($"'{currentCustomer}' 손님, 업무를 처리합니다.");
}
Console.WriteLine($"남은 대기 인원: {waitingQueue.Count}명");
[실행 결과]
다음 손님은 '박철수' 입니다.
'박철수' 손님, 업무를 처리합니다.
'이민수' 손님, 업무를 처리합니다.
'김유리' 손님, 업무를 처리합니다.
남은 대기 인원: 0명
스택(Stack)은 큐와 정반대의 개념입니다. 가장 나중에 들어간 데이터가
가장 먼저 나온다는 후입선출(LIFO: Last-In, First-Out) 원칙을 따릅니다.
식당에서 갓 씻은 접시를 쌓아두는 것을 상상해 보세요.
우리는 항상 맨 위에 있는 접시, 즉 가장 마지막에 쌓아 올린 접시부터 사용합니다.
맨 아래에 있는 접시를 꺼내려면 위의 접시를 모두 치워야 하죠.
[코드]
using System;
using System.Collections.Generic; // Stack<T>를 사용하려면 필요합니다.
// 상황: 웹 브라우저 '뒤로 가기' 기능
Stack<string> browserHistory = new Stack<string>();
// 1. 사용자가 페이지를 탐색 (Push)
browserHistory.Push("google.com");
browserHistory.Push("velog.io");
browserHistory.Push("github.com"); // 현재 페이지
Console.WriteLine($"현재 페이지: {browserHistory.Peek()}");
// 2. '뒤로 가기' 버튼 클릭 (Pop)
Console.WriteLine("\n--- '뒤로 가기' 시작! ---");
while (browserHistory.Count > 0)
{
string previousPage = browserHistory.Pop();
Console.WriteLine($"'{previousPage}' 페이지에서 뒤로 갑니다.");
if (browserHistory.Count > 0)
{
Console.WriteLine($" -> 현재 페이지: {browserHistory.Peek()}");
}
else
{
Console.WriteLine(" -> 초기 페이지로 이동합니다.");
}
}
[실행 결과]
현재 페이지: github.com
--- '뒤로 가기' 시작! ---
'github.com' 페이지에서 뒤로 갑니다.
-> 현재 페이지: velog.io
'velog.io' 페이지에서 뒤로 갑니다.
-> 현재 페이지: google.com
'google.com' 페이지에서 뒤로 갑니다.
-> 초기 페이지로 이동합니다.
딕셔너리(Dictionary)는 데이터를 '키(Key)'와 '값(Value)'으로 묶어 저장하는 컬렉션입니다.
고유한 '키(Key)'를 통해 '값(Value)'을 매우 빠른 속도로 찾아올 수 있습니다.
내부적으로 해시 테이블(Hash Table) 구조를 사용하여, 데이터의 양과 상관없이
평균 O(1)의 시간 복잡도(매우 빠른 속도)로 값을 찾을 수 있습니다.
우리는 'apple'이라는 단어(Key)를 찾고 '사과'라는 뜻(Value)을 바로 찾아냅니다.
사전의 첫 페이지부터 한 장씩 넘겨보지 않습니다.
딕셔너리(Dictionary)는 이처럼 매우 빠른 탐색 속도를 자랑합니다.
키(Key)는 반드시 고유해야 합니다.
Add(key, value): 새로운 키-값 쌍을 추가합니다.딕셔너리[key]: 키를 이용해 값을 읽거나 수정합니다. (배열의 인덱스처럼 사용)ContainsKey(key): 특정 키가 존재하는지 안전하게 확인할 수 있습니다.Remove(key): 키를 이용해 해당 키-값 쌍을 제거합니다.[코드]
using System;
using System.Collections.Generic; // Dictionary<TKey, TValue>를 사용하려면 필요합니다.
// 상황: 학생 점수 관리
// Key: 학생 이름(string), Value: 점수(int)
Dictionary<string, int> studentScores = new Dictionary<string, int>();
// 1. 데이터 추가
studentScores.Add("김민준", 95);
studentScores.Add("안영호", 88);
studentScores.Add("홍길동", 99);
studentScores["신재아"] = 100; // 인덱서(indexer)를 사용한 추가/수정
// 2. 데이터 조회
Console.WriteLine($"김민준의 점수: {studentScores["김민준"]}");
// 3. 데이터 수정
studentScores["안영호"] = 92; // 안영호의 점수를 92로 수정
// 4. 데이터 삭제
studentScores.Remove("홍길동");
// 5. 안전한 조회
string studentName = "홍길동";
if (studentScores.ContainsKey(studentName))
{
Console.WriteLine($"{studentName}의 점수: {studentScores[studentName]}");
}
else
{
Console.WriteLine($"{studentName}의 점수 정보가 없습니다.");
}
// 6. 전체 데이터 순회
Console.WriteLine("\n--- 전체 학생 점수 ---");
foreach (var student in studentScores) // var는 KeyValuePair<string, int> 타입
{
Console.WriteLine($"{student.Key}: {student.Value}점");
}
[실행 결과]
김민준의 점수: 95
홍길동의 점수 정보가 없습니다.
--- 전체 학생 점수 ---
김민준: 95점
안영호: 92점
신재아: 100점
이제 우리는 데이터의 목적과 규칙에 따라 적절한 컬렉션을 선택할 수 있게 되었습니다.
| 제네릭 컬렉션 | 자료 구조(Data Structure) | 주 사용처 |
|---|---|---|
Queue | 선입선출(FIFO) | 대기열, 순차적 작업 처리, 너비 우선 탐색(BFS) |
Stack | 후입선출(LIFO) | 실행 취소, 재귀 알고리즘, 깊이 우선 탐색(DFS) |
Dictionary | 해시 테이블(Hash Table) | 고유 키를 통한 빠른 데이터 검색 및 관리 |