카테고리 없음

내일배움캠프 70일차 TIL 최종 프로젝트 - SortedDictionary

joseph2518 2024. 12. 20. 23:24

20241220 / Unity_6차  15주차 금요일

 

 

이번에는 방벽 생성/파괴 시 베이스 영역을 변경해 주기 위해 SortedDictionary를 사용하였다.

 

사용 이유는 가장 바깥쪽의 방벽이 생성/파괴 되었는지 확인하기 위해 정렬 기능이 필요했기 때문이다.

 

 

SortedDictionary<tkey, tvalue>

특징

  • 키를 정렬된 순서로 유지하며, 빠른 검색, 삽입, 삭제를 제공하는 C#의 컬렉션 클래스
  • 내부적으로 이진 탐색 트리(Balanced Binary Search Tree)를 기반으로 동작

 

대표적인 메서드

메서드 설명
Add(TKey key, TValue value) 지정된 키와 값을 추가.
Remove(TKey key) 지정된 키를 삭제.
TryGetValue(TKey key, out value) 키가 존재하면 값을 반환하고, 없으면 false를 반환.
ContainsKey(TKey key) 지정된 키가 있는지 확인.
ContainsValue(TValue value) 지정된 값이 있는지 확인. (효율적이지 않음, O(n))
Clear() 모든 키-값 쌍을 삭제.
Keys 정렬된 키의 컬렉션을 반환.
Values 정렬된 값의 컬렉션을 반환.
Count 딕셔너리에 있는 키-값 쌍의 개수를 반환.

 

 

 

 

사용 예시를 베이스 좌측 벽으로 들면

SortedDictionary<float, BuildableArchitecture> wallPositionLeft

private void WallConstructed(BuildableArchitecture wall)
{
    float position = wall.transform.position.x;
    
    // 벽이 좌측에 있는 경우
    if (position <= allyBase.left)
    {
    	// 기존의 가장 왼쪽 벽과 비교
        if (wallPositionLeft.Count > 0)
        {
            if (position < wallPositionLeft.Keys.First())
            {
                allyBase.left = position;
                OnAllyBaseChanged?.Invoke();
            }
        }
        else
        {
            allyBase.left = position;
            OnAllyBaseChanged?.Invoke();
        }
        wallPositionLeft.Add(position, wall);
    }
}

 

기존 가장 좌측 벽의 좌표를 wallPositionLeft.Keys.First() 으로 가져올 수 있다.

 

 

 

가장 왼쪽 벽이 부서졌을 때 베이스 좌측 경계를 기존에 왼쪽에서 2번째였던 벽의 좌표로 갱신하기 위해

SortedDictionary의 2번째 Key값을 알아낼 필요가 있었다.

 

찾아낸 방법은 3가지다.

// Enumerator 순회
flot result1;
var enumerator = dictionary.GetEnumerator();
enumerator.MoveNext(); // 첫 번째
enumerator.MoveNext(); // 두 번째
result1 = enumerator.Current.Key;

// List 변환 후 인덱스 접근
flot result2;
var keys = new List<TKey>(dictionary.Keys);
result2 = keys[1];

// Keys.ElementAt
flot result3;
result3 = dictionary.Keys.ElementAt(1);

 

각 방식의 장단점을 비교해 보았다.

방법 장점 단점
Enumerator 순회
- 메모리 효율적.
- 추가 변환 없이 뒤에서부터 탐색 가능.
- 두 번째 요소까지 O(2) 순회.
- 다소 번거로운 코드.
List 변환 후 인덱스 접근 - 인덱스 기반으로 빠르게 접근 가능(O(1)). - 키를 List로 변환하는 데 추가 메모리 사용.
- 변환 자체에 O(n) 시간 소요.
Keys.ElementAt - 직관적이고 간단한 코드.
- 추가 메모리 사용 없음.
- 중간 값 접근에 효율적(O(log n)).
- 트리 구조에서 인덱스를 찾기 위해 log n 연산이 필요.
- 느린 인덱스 접근.

 

List 변환만 피하면 될 것 같아서 그냥 코드가 단순한 ElementAt 메서드를 사용했다.

float position = wall.transform.position.x;
if (position <= allyBase.left)
{
    if (wallPositionLeft.Count > 0)
    {
        if (wall == wallPositionLeft.First().Value)
        {
            if (wallPositionLeft.Count >= 2)
            {
                allyBase.left = wallPositionLeft.Keys.ElementAt(1);
            }
            else
            {
                allyBase.left = initialBaseLeft;
            }
            OnAllyBaseChanged?.Invoke();
        }
        wallPositionLeft.Remove(position);
    }
}

 

그런데 다시 생각해 보니 요소 삭제부터 하고 First값을 받아오면 될 것 같다는 느낌이 들었다.

bool isChanged = false;

float position = wall.transform.position.x;
if (position <= allyBase.left)
{
    if (wallPositionLeft.Count > 0)
    {
        if (wall == wallPositionLeft.First().Value) isChanged = true;

        wallPositionLeft.Remove(position);

        if (isChanged == true)
        {
            if (wallPositionLeft.Count > 0)
            {
                allyBase.left = wallPositionLeft.Keys.First();
            }
            else
            {
                allyBase.left = initialBaseLeft;
            }
            OnAllyBaseChanged?.Invoke();
        }
    }
}

 

두 코드를 비교하면 성능과 가독성 측면에서 모두 바뀐 이후의 코드가 더 좋아 보인다.