List, Dictionary, HashTable등 C#에서 자주 사용하는 자료구조 클래스에 대해
정리해볼 예정.
프로그래밍에서 데이터(자료)를 효율적으로 관리하는 것이 매우 중요한데
자료구조란 데이터를 구조적으로 표현하고 구현하는 중요한 알고리즘을 뜻한다.
Collections은 C#에서 지원하는 자료구조 클래스이다.
성능 이유로 현재 C#에서는 Generic Collections 사용한다.
*.net 2.0이하에서는 non-Generic Collections 사용했는데
속도와 타입안정성의 문제가 있었다.
데이터를 저장할 때 무조건 System.Objects 타입으로 저장을 하면서
데이터 저장, 추출할 때 형변환이 반드시 필요했으며 정확한 타입을 저장하지 않음으로
오류 발생의 여지가 현저히 높았다.
그 외에 스레드로부터 안전하거나 변경할 수 없는 컬렉션 옵션에는
ConcurrentDictionary<TKey, TValue>
, ImmutableList<T>
, ConcurrentQueue<T>
, ConcurrentStack<T>
, ImmutableSortedDictionary<TKey, TValue> 등이 있다.
Key와 Value로 값을 빠르게 조회할 수 있도록 하는 Collections Class
static void Main(string[] args)
{
Dictionary<string, string> dicColor = new Dictionary<string, string>();
dicColor.Add("Red", "빨강");
dicColor.Add("Green", "초록");
dicColor.Add("Blue", "파랑");
try
{
if (dicColor.ContainsKey("Red"))//RED 들어있는지 체크
{
Console.WriteLine($"dicColor[Red] 이미 존재합니다. -> {dicColor["Red"]}");
}
else
{
dicColor.Add("Red", "빨강");
}
if (!dicColor.ContainsKey("Yellow"))//YELLOW 들어있는지 체크
{
dicColor.Add("Yellow", "노랑");
}
foreach (var keyValuePair in dicColor)
{
Console.WriteLine($"dicColor[{keyValuePair.Key}] = {keyValuePair.Value}");
}
if (dicColor.ContainsKey("Red"))//RED 들어있으면 꺼내기
{
Console.WriteLine($"dicColor[Red] = {dicColor["Red"]}");
}
dicColor.Add("Blue", "파랑");//BLUE 들어있는지 체크 (Exception)
Console.WriteLine($"dicColor[Pink] = {dicColor["Pink"]}");//Yellow 있으면 출력 (Exception)
}
catch(Exception ex)
{
Console.WriteLine($"error {ex.Message}");
}
}
인덱스별로 관리 할 수 있는 동적 Collection
public class Part : IEquatable<Part>
{
public string PartName { get; set; }
public int PartId { get; set; }
public override string ToString()
{
return $"ID : {PartId} Name : {PartName}";
}
public override bool Equals(object obj)
{
if(obj is null)
{
return false;
}
Part objAsPart = obj as Part;
if(objAsPart is null)
{
return false;
}
else
{
return Equals(objAsPart);
}
}
public override int GetHashCode()
{
return PartId;
}
public bool Equals([AllowNull] Part other)
{
if(other is null)
{
return false;
}
return (this.PartId.Equals(other.PartId));
}
}
static void Main(string[] args)
{
List<Part> parts = new List<Part>();
parts.Add(new Part() { PartName = "A_Part", PartId = 1 });
parts.Add(new Part() { PartName = "B_Part", PartId = 2 });
parts.Add(new Part() { PartName = "C_Part", PartId = 3 });
parts.Add(new Part() { PartName = "D_Part", PartId = 4 });
parts.Add(new Part() { PartName = "E_Part", PartId = 5 });
foreach (Part part in parts)
{
Console.WriteLine(part);
}
Console.WriteLine();
parts.Contains(new Part { PartName = "", PartId = 6 });
parts.Insert(2, new Part() { PartName = "F_Part", PartId = 6 });
foreach (Part part in parts)
{
Console.WriteLine(part);
}
Console.WriteLine();
parts.Remove(new Part() { PartId = 3, PartName = "C_Part_1" });
foreach (Part part in parts)
{
Console.WriteLine(part);
}
Console.WriteLine();
parts.RemoveAt(3);
foreach (Part part in parts)
{
Console.WriteLine(part);
}
}
Queue는 먼저 추가된 데이터가 먼저 출력 처리되는 (FIFO, First In First Out) 자료 구조이다.
Queue는 맨 뒤(tail) 에 데이터를 계속 추가하고, 맨 앞(head)에서만 데이터를 읽기 때문에 순차적으로 데이터를 처리 한다.
static void Main(string[] args)
{
var q = new ConcurrentQueue<int>();
// 데이터를 큐에 넣는 쓰레드
Task tEnq = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100; i++)
{
q.Enqueue(i);
Thread.Sleep(1000);
}
});
//데이터를 큐에서 읽는 쓰레드
Task tDeq = Task.Factory.StartNew(() =>
{
int n = 0;
int result;
while (n < 100)
{
if (q.TryDequeue(out result))
{
Console.WriteLine(result);
n++;
}
Thread.Sleep(1000);
}
});
Task.WaitAll(tEnq, tDeq);
}
Stack은 가장 나중에 추가된 데이터가 먼저 출력 처리되는 (LIFO, Last In First Out)자료 구조로서 가장 최신 입력된 순서대로 처리해야 하는 상황에서 사용.
스택은 개념적으로 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 구조로 돼있다.
저장은 Push , 꺼내는 것은 Pop이라 한다.
static void Main(string[] args)
{
var s = new ConcurrentStack<int>();
// 데이터를 스택에 넣는 쓰레드
Task tPush = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 100; i++)
{
s.Push(i);
Thread.Sleep(100);
}
});
// 데이터를 스택에서 읽는 쓰레드
Task tPop = Task.Factory.StartNew(() =>
{
int n = 0;
int result;
while (n < 100)
{
if (s.TryPop(out result))
{
Console.WriteLine(result);
n++;
}
Thread.Sleep(150);
}
});
// 두 쓰레드가 끝날 때까지 대기
Task.WaitAll(tPush, tPush);
}
LinkedList는 데이터를 포함하는 노드를 연결하여 컬렉션을 만든 자료 구조로서 각 노드는 데이터와 다음/이전 링크 포인터를 갖는다.
특정 노드 삽입 => O(1)
특정 노드 검색 => O(n)
이중 연결리스트인 LinkedList<T>를 이용해 보았다.
static void Main(string[] args)
{
LinkedList<string> list = new LinkedList<string>();
//끝에 계속 추가
list.AddLast("Apple");
list.AddLast("Banana");
list.AddLast("Lemon");
LinkedListNode<string> node = list.Find("Banana");
LinkedListNode<string> newNode = new LinkedListNode<string>("Grape");
//새 Grape 노드를 Banana 노드 뒤에 추가
list.AddAfter(node, newNode);
// 리스트 출력
list.ToList<string>().ForEach(p => Console.WriteLine(p));
// Enumerator 리스트 출력
foreach (var item in list)
{
Console.WriteLine(item);
}
}
키를 기준으로 자동 정렬되며, 키/값. 쌍의 컬렉션을 나타내는 자료구조.
SortedList<TKey, TValue>를 이용해 보았다.
static void Main(string[] args)
{
#region 1. 키가 숫자일 경우 숫자 정렬
SortedList<int, string> sortedList1 = new SortedList<int, string>();
sortedList1.Add(3, "Three");
sortedList1.Add(1, "One");
sortedList1.Add(2, "Two");
sortedList1.Add(4, "Four");
foreach (KeyValuePair<int, string> kvp in sortedList1)
{
Console.WriteLine($"{kvp.Key} / {kvp.Value}");
}
foreach (var kvp in sortedList1)
{
Console.Write("{0, -10} ", kvp);
}
Console.WriteLine();
#endregion
#region 2. 키가 문자일 경우 문자정렬
SortedList<string, int> sortedList2 = new SortedList<string, int>();
sortedList2.Add("Three", 3);
sortedList2.Add("One", 1);
sortedList2.Add("Two", 2);
sortedList2.Add("Four", 4);
foreach (KeyValuePair<string, int> kvp in sortedList2)
{
Console.WriteLine($"{kvp.Key} / {kvp.Value}");
}
foreach (var kvp in sortedList2)
{
Console.Write("{0, -10} ", kvp);
}
Console.WriteLine();
#endregion
#region 3. TryGetValue 함수 사용
SortedList<string, int> sortedList3 = new SortedList<string, int>();
sortedList3.Add("Three", 3);
sortedList3.Add("One", 1);
sortedList3.Add("Two", 2);
sortedList3.Add("Four", 4);
int val;
if(sortedList3.TryGetValue("Ten", out val))
{
Console.WriteLine($"key : Ten, value : {val}");
}
else
{
Console.WriteLine("[Ten] : key is not valid");
}
if(sortedList3.TryGetValue("One", out val))
{
Console.WriteLine($"key : One, value : {val}");
}
#endregion
#region 4. 매개변수를 키로 하는 요소 / 매개변수를 값으로 하는 요소
SortedList<string, int> sortedList4 = new SortedList<string, int>();
sortedList4.Add("Three", 3);
sortedList4.Add("One", 1);
sortedList4.Add("Two", 2);
sortedList4.Add("Four", 4);
//매개변수를 키로
Console.WriteLine(sortedList4.ContainsKey("One"));
Console.WriteLine(sortedList4.ContainsKey("Ten"));
//매개변수를 값으로
Console.WriteLine(sortedList4.ContainsValue(2));
Console.WriteLine(sortedList4.ContainsValue(6));
#endregion
#region 5. 삭제하는 법
SortedList<string, int> sortedList5 = new SortedList<string, int>();
sortedList5.Add("Three", 3);
sortedList5.Add("One", 1);
sortedList5.Add("Two", 2);
sortedList5.Add("Four", 4);
sortedList5.Remove("Three");//키가 "Three" 삭제
sortedList5.RemoveAt(0);//첫번째 요소
foreach (KeyValuePair<string, int> kvp in sortedList5)
{
Console.Write("{0, -10} ", kvp);
}
Console.WriteLine();
#endregion
}
https://docs.microsoft.com/ko-kr/dotnet/standard/collections/
http://www.csharpstudy.com/