컬렉션 사용하기
배열과 같이 특정 항목들의 집합을 리스트 또는 컬렉션이라고 합니다. 이번 강의는 닷넷에서 제공하는 유용한 컬렉션 관련 클래스들에 대해서 학습합니다.
> // 컬렉션(Colleciton): 배열, 리스트, 사전을 사용하여 관련 개체의 그룹을 만들고 관리
배열(Array)과 컬렉션(Collection)
C#에서는 배열(Array)과 컬렉션(Collection) 그리고 리스트(List)는 학습 레벨에서는 동일하게 취급합니다. 컬렉션 클래스는 데이터 항목들의 집합을 메모리 상에서 다루는 클래스로 문자열과 같은 간단한 형태도 있고, 특정 클래스 형식의 집합으로 복잡한 형태로 구분됩니다.
컬렉션(Collection)의 종류
배열을 학습할 때에도 잠깐 언급했지만, C#의 컬렉션은 다음 세종류로 구분합니다.
- 배열(Array): 일반적으로 숫자와 같이 간단한 데이터 형식을 저장합니다.
- 리스트(List): 간단한 데이터 형식을 포함한 개체들을 저장합니다.
- 사전(Dictionary): 키와 값의 쌍으로 관리되는 개체들을 저장합니다.
일반적으로 기본형에 대한 그룹을 배열로 보고 새로운 타입(클래스)의 그룹을 컬렉션으로 비교하기도 합니다.
- 배열(Array): 정수형, 문자열 등의 집합을 나타냅니다.
- 컬렉션(Collection): 개체들의 집합을 나타냅니다. 리스트, 집합, 맵, 사전도 컬렉션과 같은 개념으로 사용됩니다.
데이터를 저장하는 형태로 볼 때 배열, 리스트, 컬렉션은 동일한 의미를 가집니다.
코드: ListDemo.cs
> //[?] 리스트(배열, 컬렉션)
> string[] colors = { "red", "green", "blue" };
> colors.Length
3
> colors[0]
"red"
> colors[1]
"green"
> colors[2]
"blue"
데이터를 그룹으로 묶어서 관리할 때에는 일반적으로 배열(Array)로 관리합니다. 배열은 크기가 고정되어 있습니다. 배열은 크기가 고정되어 있어 새로운 데이터를 추가할 수 없습니다. 이러한 단점을 제거한 것이 바로 컬렉션입니다.
- 컬렉션은 형식 안정성(Type Safety)을 유지하면서 데이터를 동적으로 관리할 수 있는 구조를 제공합니다. 즉, 크기가 동적으로 변경될 수 있어, 요소를 추가하거나 제거하는 것이 용이합니다.
- 또한, 컬렉션은 데이터를 조회, 정렬, 중복 제거하는 기능뿐만 아니라, 키(Key)와 값(Value)을 쌍으로 관리하는 기능 등 다양한 유용한 기능들을 제공합니다.
닷넷에는 컬렉션 관련 다음과 같은 여러 클래스들 제공합니다.
Stack
클래스Queue
클래스ArrayList
클래스
앞서 미리 살펴본 정적인 멤버를 가지는 Math
클래스와 달리 컬렉션 관련 클래스들을 사용하려면 먼저 클래스의 인스턴스를 선언해야 합니다.
.NET 컬렉션의 역사
닷넷에는 많은 양의 컬렉션 클래스들이 있습니다. 아래 단계별 요약은 간단히 훑어보고 넘어가면 됩니다.
- 1.0 버전
ArrayList
:object
형식으로 데이터 받는 컬렉션 클래스
- 2.0 버전: 제네릭 클래스 제공(필요한 형식으로 데이터 받는 컬렉션 클래스)
HashSet<T>
Stack<T>
Queue<T>
LinkedList<T>
List<T>
Dictionary<TKey, TValue>
- 3.0 버전
- LINQ
- 4.0 버전
- Concurrent Collections (
ConcurrentDictionary<TKey, TValue>
,ConcurrentQueue<T>
,ConcurrentStack<T>
,BlockingCollection<T>
)
- Concurrent Collections (
- 4.5 버전
- 읽기전용 인터페이스 (
IReadOnlyList<T>
,IReadOnlyCollection<T>
) - Immutable Collections (
ImmutableList<T>
,ImmutableDictionary<TKey, TValue>
,ImmutableQueue<T>
,ImmutableStack<T>
)
- 읽기전용 인터페이스 (
- 7.X 버전
Span<T>
- 8.0 버전
Memory<T>
:Span<T>
와 유사하지만, 힙 할당 가능ReadOnlyMemory<T>
: 읽기 전용 메모리 관리FrozenDictionary<TKey, TValue>
: 한 번 설정하면 불변(Immutable)으로 동작하는 고성능Dictionary
FrozenSet<T>
: 한 번 설정하면 불변으로 동작하는 고성능HashSet<T>
컬렉션 종류
컬렉션(리스트) 관련 클래스들은 다음과 같이 몇 가지 네임스페이스에 존재합니다. 각각의 네임스페이스에는 다음 목록에 제시한 클래스를 포함하여 더 많은 클래스들이 있습니다.
System
네임스페이스Array
클래스
System.Collections
네임스페이스Stack
클래스Queue
클래스ArrayList
클래스
System.Collections.Generic
네임스페이스List<T>
클래스LinkedList<T>
클래스Stack<T>
클래스Queue<T>
클래스
System.Collections.Concurrent
네임스페이스ConcurrentStack<T>
클래스ConcurrentQueue<T>
클래스
리스트 출력 구문
리스트를 출력할 때에는 리스트에 담긴 모든 요소를 사용할 경우에는 foreach
문, 필요한 영역의 리스트를 사용할 때에는 for
문을 사용하면 좋습니다.
foreach
문:
- 빠르고 쉽습니다.
- 모든 요소를 반복 출력합니다. 단, 필요한 속성만 선별해서 출력 가능합니다.
for
문:
- 복잡하지만 유연합니다.
- 모든 요소 또는 필요한 영역의 요소를 반복 출력합니다. 요소에 읽고 쓰기가 가능합니다.
Array
클래스
컬렉션 관련 클래스를 다루기에 앞서 먼저 배열 사용에 있어 유용한 기능을 제공하는 Array
클래스를 먼저 알아보겠습니다. Array
클래스는 배열을 매개 변수로 받아 정렬, 역순, 변환 등의 작업을 진행합니다.
Array
클래스의 주요 메서드는 다음과 같습니다.
Array.Sort()
: 배열을 정렬합니다.Array.Reverse()
: 배열을 역순으로 바꿉니다.Array.ConvertAll()
: 배열을 특정 값으로 변환합니다.
Array
클래스의 Sort
메서드로 배열 정렬하기
다음과 같이 arr
배열에 정수 데이터가 무작위로 저장되었을 때 Array.Sort()
메서드에 배열을 매개 변수로 입력 후 실행하면 arr
배열이 정렬됩니다.
코드: ArraySort.cs
> int[] arr = { 3, 2, 1, 4, 5 };
> Array.Sort(arr); // 정렬
> foreach (var item in arr)
. {
. Console.WriteLine(item);
. }
1
2
3
4
5
Array
클래스의 Reverse
메서드로 배열을 거꾸로 변환하기
이번에는 Array
클래스의 Reverse
메서드를 사용해보겠습니다.
코드: ArrayClassDemo.cs
> //[?] Array 클래스의 Reverse 메서드로 배열을 거꾸로 변환하기
> int[] arr = { 1, 2, 3 };
> Array.Reverse(arr); // 배열을 역순으로 변환
> arr
int[3] { 3, 2, 1 }
코드 실행 결과처럼 Array
클래스의 여러 메서드 중에서 Reverse()
메서드를 사용하면 배열에 저장된 데이터를 역순으로 반환합니다.
Array
클래스의 ConvertAll
메서드로 형식 변환하기
Array.ConvertAll()
메서드를 사용하면 숫자 모양의 문자열 배열을 정수형 배열로 변경할 수 있습니다.
숫자 형태로 저장된 문자열 배열을 정수형 배열로 변경할 때에는 다음과 같이 사용합니다.
코드: ArrayConvertAll.cs
> string[] strArr = { "10", "20", "30" };
> int[] intArr = Array.ConvertAll(strArr, int.Parse);
> foreach (var number in intArr)
. {
. Console.WriteLine(number);
. }
10
20
30
사실, Array.ConvertAll()
메서드는 자주 사용하지 않습니다. 참고로만 살펴보면 됩니다.
컬렉션 클래스
닷넷에는 Stack
, Queue
, ArrayList
, Hashtable
등의 자료 구조를 다루는 컬렉션 클래스를 제공합니다.
Stack
클래스Queue
클래스ArrayList
클래스Hashtable
클래스
스택(Stack) 클래스
스택 클래스는 단어 그대로 음식을 담는 접시와 같이 아래에서 위로 데이터를 쌓는 형태의 자료 구조를 다룹니다. 스택 클래스는 LIFO(Last In First Out, 후입선출)의 특성을 보이는데 나중에 들어온 데이터가 먼저 출력되는 자료 구조입니다.
Stack
클래스의 주요 속성 및 메서드는 다음과 같습니다.
Count
: 스택에 있는 데이터 개수 조회Push()
: 스택에 데이터 저장하기Pop()
: 스택에서 데이터 꺼내기
스택 구조를 다룰 때 가장 많이 나오는 단어는 Overflow(오버플로우)입니다. 스택 오버플로우는 스택이 꽉 차는 것을 의미합니다.
Stack
클래스 사용하기
Stack
클래스에 대한 다음 예제는 StackClass.cs에서 볼 수 있습니다.
먼저 Stack
클래스를 사용하려면 System.Collections
네임스페이스를 포함합니다.
> using System.Collections;
Stack
클래스의 인스턴스를 stack
이름으로 생성합니다. Stack
클래스의 인스턴스인 소문자 stack
은 일반적으로 stack 개체로 읽습니다.
> Stack stack = new Stack();
Stack
클래스는 Push()
메서드로 object
형식의 데이터를 저장할 수 있습니다.
> stack.Push("First");
> stack.Push("Second");
Stack
클래스에 저장된 데이터는 Pop()
메서드를 통해서 읽어올 수 있습니다. 이때 나중에 입력된(Push) 데이터가 먼저 출력됩니다.
> stack.Pop()
"Second"
> stack.Pop()
"First"
만약, 스택에 저장된 데이터가 아무것도 없는데 Pop()
메서드를 요청하면 다음과 같이 에러가 발생합니다.
> stack.Pop()
스택이 비어 있습니다.
+ System.Collections.Stack.Pop()
Stack
클래스의 주요 멤버 사용하기
이번에는 프로젝트를 만들어 Stack
클래스를 사용해보겠습니다.
코드: StackNote.cs
// Stact 클래스: LIFO(Last In First Out) 형태의 데이터 저장
// Push(): 스택 구조에 데이터 입력
// Pop(): 스택 구조에서 데이터 출력
// Overflow: 스택이 가득찼을 때 발생
// Underflow: 스택이 비었을 때 발생
using System;
using System.Collections; // 주요 자료구조 관련 클래스들
class StackNote
{
static void Main()
{
//[1] `Stack` 클래스의 인스턴스 생성
Stack stack = new Stack();
//[2] 데이터 입력
stack.Push("첫 번째");
stack.Push("두 번째");
stack.Push("세 번째");
//[3] 데이터 출력
Console.WriteLine(stack.Pop()); // 세 번째
Console.WriteLine(stack.Pop()); // 두 번째
Console.WriteLine(stack.Pop()); // 첫 번째
//[!] 비어있는 스택에서 Pop() 요청하면 에러
try
{
Console.WriteLine(stack.Pop()); // 언더플로우 에러
}
catch (Exception ex)
{
Console.WriteLine($"예외 내용: {ex.Message}");
}
}
}
세 번째
두 번째
첫 번째
예외 내용: Stack empty.
Stack
클래스에 Push()
메서드로 데이터를 입력한 후 이를 Pop()
메서드로 꺼내어 쓰는 내용입니다. 더 이상 스택에 데이터가 없을 땐 "Stack empty." 예외가 발생합니다. 스택에 너무 많은 데이터가 저장되어 스택이 꽉 차면 "스택 오버플로우" 에러가 발생합니다.
Stack
클래스의 Peek()
메서드로 최근 데이터만 가져오기
이번에는 Stack
클래스의 또 다른 메서드를 사용해보겠습니다.
코드: StackDemo.cs
> //[?] `Stack` 클래스: LIFO(Last In First Out) 형태의 데이터 보관
> using System.Collections;
>
> //[1] Stack 개체 만들기
> Stack stack = new Stack();
>
> //[2] Push()로 데이터 저장
> stack.Push("닷넷노트");
> stack.Push("닷넷코리아");
> stack.Push("VisualAcademy");
>
> //[3] Peek()로 제일 상단(마지막)의 데이터 반환
> $"{stack.Peek()}, {stack.Count}"
"VisualAcademy, 3"
>
> //[4] Pop()로 현재 스택의 가장 마지막 데이터 제거
> stack.Pop();
>
> //[5] 스택의 마지막 데이터 반환: 만약 스택이 비어있을 때에는 에러 발생
> $"{stack.Peek()}, {stack.Count}"
"닷넷코리아, 2"
>
> //[6] Count로 스택의 데이터 개수를 확인
> if (stack.Count > 0)
. {
. stack.Pop(); // 가장 마지막 데이터 제거
. Console.WriteLine($"{stack.Peek()}, {stack.Count}");
. }
닷넷노트, 1
>
> //[7] Clear()로 스택 비우기
> stack.Clear(); // 비우기
> stack.Count
0
Stack
클래스의 Peek()
메서드는 스택의 상단에 있는 데이터를 가져오지만 Pop()
메서드처럼 제거하지는 않습니다. 스택에 데이터를 넣어 놓고 계속해서 사용만 할 때에는 Peek()
메서드가 사용됩니다.
큐(Queue
) 클래스
큐 클래스는 스택 클래스와 달리 먼저 들어온 데이터가 먼저 나옵니다. 일반적으로 은행에서 먼저 온 사람을 처리해주는 것과 같이 큐(Queue)라는 단어는 기다림 통로(은행 줄서기) 또는 FIFO(First In First Out, 선입선출)로 표현되며 먼저 들어온 게 먼저 나가는 형태의 데이터를 다룹니다.
이번에는 Queue
클래스를 사용해보겠습니다.
코드: QueueDemo.cs
> //[?] Queue: FIFO(First In First Out) 형태의 데이터 보관: 대기행렬(은행 줄서기)
> // Enqueue(): 큐에 데이터 저장(QueueIn)
> // Dequeue(): 큐에서 데이터 출력(QueueOut)
> using System.Collections;
>
> //[1] Queue 클래스의 인스턴스 생성
> var queue = new Queue();
>
> //[2] 큐(대기행렬)에 데이터 입력: Enqueue()
> queue.Enqueue(10);
> queue.Enqueue(20);
> queue.Enqueue(30);
>
> //[3] 큐에서 데이터 출력: Dequeue()
> queue.Dequeue()
10
> queue.Dequeue()
20
> queue.Dequeue()
30
> queue.Dequeue()
System.InvalidOperationException: 큐가 비어 있습니다.
+ System.Collections.Queue.Dequeue()
큐 개체에 Enqueue()
메서드를 사용하여 10, 20, 30
형태로 데이터를 저장한 후 Dequeue()
메서드를 호출하면 다시 10, 20, 30
형태로 순서대로 데이터가 출력되는 구조를 가지는게 바로 큐 클래스입니다.
ArrayList
클래스
C# 1.0부터 제공되던 ArrayList
클래스는 컬렉션 형태의 데이터를 저장하고 관리하는 여러 편리한 API를 제공합니다. 다만, C# 2.0부터 제공되는 ArrayList
클래스보다 더 기능이 향상된 제네렉(Generic) 관련 컬렉션 클래스를 제공하기에 이번 예제 이후로는 ArrayList
클래스는 사용하지 않습니다.
코드: ArrayListNote.cs
> using System.Collections;
>
> ArrayList list = new ArrayList();
> list.Add("C#");
> list.Add("TypeScript");
>
> for (int i = 0; i < list.Count; i++)
. {
. Console.WriteLine(list[i].ToString());
. }
C#
TypeScript
ArrayList
클래스의 인스턴스인 list
를 생성 후 Add()
메서드를 통해서 문자열 등을 저장할 수 있습니다. 그런 다음 for
문 등을 통해서 list[i]
형태로 ArrayList
에 저장되어 있는 값을 읽어서 사용할 수 있는 형태입니다. ArrayList
의 Add()
메서드는 매개 변수로 object
형식을 받기에 문자열을 포함한 C#의 모든 데이터 형식을 저장하고 사용할 수 있습니다. 이번 예제에서는 다루지 않겠지만 Add()
로 추가된 항목은 Remove()
와 같은 메서드로 제거할 수 있습니다.
ArrayListDemo.cs
ArrayListDemo_ArrayList 컬렉션 클래스 사용 데모
코드: ArrayListDemo.cs
using System;
using System.Collections;
namespace ArrayListDemo
{
class ArrayListDemo
{
static void Main()
{
Car car1 = new Car(); car1.Make = "Benz"; car1.Model = "S600";
Car car2 = new Car(); car2.Make = "BMW"; car2.Model = "BMW7";
Book book1 = new Book();
book1.Author = "박용준"; book1.Title = "쉽게 배우는"; book1.ISBN = "1234";
ArrayList lists = new ArrayList(); // 개체형을 담을 수 있는 컬렉션(배열)
lists.Add(car1);
lists.Add(car2);
lists.Add(book1); // 저장
lists.Remove(book1); // 삭제(삭제 안하면 아래 출력 코드에서 에러 발생)
// 출력
foreach (object o in lists)
{
Console.WriteLine("{0}, {1}", ((Car)o).Make, ((Car)o).Model);
}
}
}
class Car
{
public string Make { get; set; }
public string Model { get; set; }
}
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string ISBN { get; set; }
}
}
Hashtable
클래스
Hashtable
클래스는 정수 인덱스 및 문자열 인덱스를 사용할 수 있습니다. 이번에는 해시테이블(Hashtable) 클래스를 사용해보겠습니다.
코드: HashtableDemo.cs
> //[?] `Hashtable` 클래스를 사용하여 정수 형식 또는 문자열 형식 인덱스 사용하여 값 저장하기
> //[1] Hashtable의 인스턴스 생성
> Hashtable hash = new Hashtable();
>
> //[2] 배열형 인덱서를 사용 가능한 구조 및 문자열 인덱스 사용 가능
> hash[0] = "닷넷코리아"; //[A] 배열과 같은 n번째 형태 사용 가능
> hash["닉네임"] = "레드플러스"; //[B] 문자열 인덱스 사용 가능
> hash["사이트"] = "VisualAcademy";
>
> //[3] 직접 출력
> hash[0]
"닷넷코리아"
> hash["닉네임"]
"레드플러스"
> hash["사이트"]
"VisualAcademy"
>
> //[4] key와 value 쌍으로 출력 가능
> foreach (object o in hash.Keys)
. {
. Console.WriteLine(hash[o]);
. }
레드플러스
VisualAcademy
닷넷코리아
해시테이블은 배열과 같이 hash[0]
, hash[1]
, … 형태로 데이터를 저장할 수 있는 특징과 함께 hash["닉네임"], hash["사이트"]와 같이 문자열 형태의 인덱스를 사용할 수 있는 특징을 가지고 있습니다.
자료 구조(데이터 구조)
프로그램에서 데이터를 저장하고 활용하는 기본 단위인 변수는 단순한 데이터 저장을 넘어, 스택(Stack), 큐(Queue), 리스트(List) 등 다양한 자료 구조를 통해 보다 효율적으로 데이터를 관리할 수 있습니다. 프로그래밍에서는 변수, 배열, 구조체뿐만 아니라 특정 형식의 데이터를 다루는 방식을 **자료 구조(Data Structure)**라고 합니다.
자료 구조의 종류
기본 자료 구조
기본적인 데이터 저장 방식으로, 프로그램에서 가장 기초적인 단위로 활용됩니다.
- 변수(Variable)
- 배열(Array)
- 구조체(Struct)
- 클래스(Class)
특수 목적 자료 구조
특정한 방식으로 데이터를 저장하고 관리하는 구조로, 효율적인 데이터 처리에 활용됩니다.
- 스택(Stack)
- 큐(Queue)
- 리스트(List)
- 트리(Tree)
- 그래프(Graph)
자료 구조 활용 및 소스 코드
자료 구조와 알고리즘은 프로그래밍 기초를 익힌 후 심화 학습하는 주요 분야입니다. 그러나 본 강의에서는 자료 구조를 직접 구현하는 대신, .NET에서 기본 제공하는 Stack
, Queue
, ArrayList
등의 클래스를 활용합니다.
추후 심화 학습을 위해 .NET에서 제공하는 Stack
및 Queue
클래스의 원본 소스를 확인하는 것도 도움이 됩니다. 해당 소스는 아래 링크에서 제공됩니다.
Stack
클래스 소스 코드:
Stack.cs - .NET Reference SourceQueue
클래스 소스 코드:
Queue.cs - .NET Reference Source
이외에도 .NET에서는 다양한 자료 구조 관련 API 소스를 공개하고 있으므로, 이를 분석해 보면 C# 실력을 향상하는 데 유용할 것입니다.
장 요약
닷넷에는 굉장히 많은 컬렉션 관련 클래스들을 제공합니다. 이 강의를 통해서 모두 다루진 않지만 자주 사용되는 클래스들은 한번씩 다뤄볼 예정입니다. Stack
, Queue
, ArrayList
와 같은 클래스들은 전통적으로 많이 사용되던 클래스이므로 반드시 기억하고 넘어가되, 뒤에서 제네릭 버전의 클래스로 대체해서 사용하면 됩니다. 자, 그러면 C#의 유용한 특징 중 하나인 제네릭을 학습해 나가도록 하겠습니다.
참고: 파이썬 리스트
코드: ListMethod.py
>>> # 좋아하는 음식
>>> foods = ["짜장면", "짬뽕", "짬짜면"]
>>> print(foods)
['짜장면', '짬뽕', '짬짜면']
>>> print(len(foods))
3
>>> foods.append("라면")
>>> print(foods)
['짜장면', '짬뽕', '짬짜면', '라면']
>>> foods.insert(0, "곰탕")
>>> print(foods)
['곰탕', '짜장면', '짬뽕', '짬짜면', '라면']
>>> del(foods[3])
>>> print(foods)
['곰탕', '짜장면', '짬뽕', '라면']
>>>