[ 🌱TSet의 구조와 활용 ]
set 과 TSet 의 활용 방법은 내부적으로 다르게 동작하기에 활용방법도 다르다. (unordered_set 과 유사하게 동작)
📍set 특징 :
- 이진 트리로 구성되어 있가에 기본적으로 정렬을 지원함
- 메모리 구성이 효율적이지 않다
- element 가 삭제될 때 균형 유지를 위해 재구축이 일어날 수 있음
- 모든 자료를 순회하는데 적합하지 않음
📍TSet 특징 :
- 해시테이블 형태로 키 데이터가 구축되어 있어 빠른 검색이 가능함
- 동적 배열(vector)의 형태로 데이터가 모여있음
- 자료 순회가 빠름
- element 를 삭제해도 재구축이 일어나지 않음
- 동적 할당으로 인해 자료구조 내에 비어있는 데이터가 존재할 수 있음
- 중복 없는 데이터 집합을 구축하는데 유용하게 사용할 수 있다.
📍TSet의 내부구조 :
기본적으로 동적 가변배열의 형태를 가지고 있다.
동적 배열로 구성되어 있기에 중간에 이빨이 빠진것 같이 비어있는 공간이 존재할 수 있다는 특징을 가진다.
언리얼 엔진의 세트 컨테이너
TSet, 세트는 보통 순서가 중요치 않은 상황에서 고유 엘리먼트를 저장하는 데 사용되는 고속 컨테이너 클래스입니다.
dev.epicgames.com

데이터 값 자체를 키로 사용한다. 즉, 내부적으로 해시 테이블로 구성되어 있기에 데이터에 빠르게 접근할 수 있다.



TSet은 이빨 빠진 동적 가변 배열의 형태를 가지고 있으며, 데이터가 들어올 때 비어있는 공간부터 들어가기에 데이터의 순서가 보장되지 않는다는 의미
[ 🌱TSet 사용법 ]
📍TSet 의 생성 :
TSet<타입> 변수명;
ex) TSet<int32> IntSet;
TArray와 비슷하게 선언만 해주면 비어있는 자료구조가 생성된다.
직접 만든 구조체를 TSet으로 관리하고자 한다면 해당 자료형에 대한 GetTypeHash() 함수를 구현해줘야한다.
📍값 추가 :
🔹Add()
기본적으로 Add 함수를 통해 값을 추가해준다.
중복값을 지원하지 않기에 중복 값을 넣게되면 아무런 변화가 생기지 않기에 어떤 동작이 발생하는지 알 수 없다.
🔹Emplace()
TArray와 마찬가지로 Emplace 함수를 사용하면 임시 변수를 생성하여 복사하는 작업 없이 값을 대입 할 수 있다. (성능 ↑)
🔹Append()
Append 함수를 사용하여 다른 Set과 병합하는것도 가능하다.
TSet<FString> StrSet;
StrSet = { TEXT("A"), TEXT("B"), TEXT("C"), TEXT("D") };
TSet<FString> Temp;
Temp = { TEXT("Hello"), TEXT("Unreal") };
StrSet.Append(Temp);
📍쿼리 :
🔹Num()
Set 컨테이너에 들어있는 element 개수를 반환한다.
TSet<FString> StrSet;
StrSet = { TEXT("A"), TEXT("B"), TEXT("C"), TEXT("D") };
int32 Cnt = StrSet.Num(); // 4
🔹Contains()
특정 값이 있는지 확인할 수 있다.
TArray와 달리 해시 테이블로 구성되어있기에 찾기 기능이 빠르다.
Set의 장점은 찾기 기능이 빠르다는 점이다.
TSet<FString> StrSet;
StrSet = { TEXT("A"), TEXT("B"), TEXT("C") };
bool IsThere1 = StrSet.Contains(TEXT("A")); // true
bool IsThere2 = StrSet.Contains(TEXT("D")); // false
🔹FSetElementId
내부적으로 해시 테이블의 ID 는 FSetElementId 구조체로 타입으로 저장되어 있다.
TSet내 element의 인덱스를 받아와서 operator[]를 통해 값에 접근할 수 있다.
Index() 함수를 찾을 수 없어서 TSet에서 제공하는 FindId() 함수로 실습해봄...
TSet<FString> StrSet;
StrSet = { TEXT("Banana"), TEXT("Apple"), TEXT("Kiwi") };
FSetElementId id = StrSet.FindId(TEXT("Banana"));
FString str = StrSet[id]; // Banana
🔹Find()
Set에 키가 있는지 확실하지 않은 경우 Contains() 함수, 그리고 operator[] 를 사용해서 검사할 수 있다.
그러나 이러한 방법이 이상적이진 않다. 왜냐하면 성공적으로 해당 값을 가져오기 위해선 같은 값에 대해 조회 작업을 두번 수행해야하기 때문이다.
Find() 함수를 사용하면 조회 동작을 한번으로 합칠 수 있다.
Find는 해당 키가 Set 컨테이너에 존재하는 경우 해당 element 를 가리키는 주소를 반환한다.
FindId() 함수를 사용하면 두단계에 거치던 작업을 Find() 함수를 사용해서 한번에 동일한 결과를 얻을 수 있다.
TSet<FString> StrSet;
StrSet = { TEXT("Banana"), TEXT("Apple"), TEXT("Kiwi") };
FString* Target = StrSet.Find(TEXT("Kiwi"));
UE_LOG(LogTemp, Log, TEXT("Target : %s"), **Target);
TSet<int32> IntSet;
IntSet = { 1,2,3,4 };
int32* Target = IntSet.Find(1);
UE_LOG(LogTemp, Log, TEXT("Target : %d"), *Target);
🔹Array()
TSet의 엘리먼트로 채워진 TArray 를 반환한다.
TSet<FString> StrSet;
StrSet = { TEXT("Banana"), TEXT("Apple"), TEXT("Kiwi") };
TArray<FString> StrArray = StrSet.Array();
📍제거 :
🔹Remove()
파라미터로 element 값을 넣어 해당하는 값을 TSet에서 제거한다.
제거된 엘리먼트의 개수를 반환한다.
아무것도 제거되지 않았다면 0을 반환한다.
TSet<FString> StrSet;
StrSet = { TEXT("Banana"), TEXT("Apple"), TEXT("Kiwi") };
int32 cnt1 = StrSet.Remove("Apple"); // 1
int32 cnt2 = StrSet.Remove("Apple"); // 0
🔹Empty(), Reset()
TSet 에 들어있는 모든 엘리먼트를 제거한다.
Empty()는 인자로 남기고자 하는 Slack 의 개수를 받아줄 수 있다. (안 넣어줘도 됨)
Reset은 항상 최대한의 Slack을 남긴다.
📍슬랙 (Slack) :
Slack(여유분)은 할당된 메모리에 값이 존재하지 않음을 의미한다.
단, TSet은 TArray 처럼 할당된 엘리먼트의 수를 확인하는 기능(Max(), GetSlack())이 제공되지 않는다.
🔹Reserve(), Reset()
Element 없이 메모리를 할당하려면 Reserve() 함수를 호출하면 된다.
Element 를 제거할 떄도 Reset() 함수를 호출하면 할당된 메모리를 해제하지 않고 값만 비운다.
TSet를 비우고 동일하거나 더 적은 수의 element 를 바로 다시 채우고자 하는 경우 효율적이다.
흠... 실습결과 문서와 달리 역순이 아니라 순서대로 들어간다.. 뭘까..?
TSet<FString> FruitSet;
FruitSet.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{
FruitSet.Add(FString::Printf(TEXT("Fruit%d"), i));
}
🔹Shrink()
Shrink() 함수의 경우 컨터이너 끝에서부터 모든 슬랙을 제거한다.
중간이나 시작 부분의 빈 엘리먼트는 놔둔다.
// 세트에서 엘리먼트를 하나 건너 하나씩 제거합니다.
for (int32 i = 0; i < 10; i += 2)
{
FruitSet.Remove(FSetElementId::FromInteger(i));
}
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0", <invalid> ]
FruitSet.Shrink();
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0" ]
🔹Compat / CompactStable
Shrink 함수를 통해 모든 슬랙을 제거하고자 했다면 먼저 Compact 또는 CompactStable 함수를 호출하여 빈 공간을 그룹으로 묶어줘야한다.
CompactStable 은 엘리먼트의 순서를 유지한 채 통합한다.
FruitSet.CompactStable();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0", <invalid>, <invalid>, <invalid>, <invalid> ]
FruitSet.Shrink();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0" ]
📍DefaultKeyFunc :
직접 제작한 구조체를 TSet 컨터이너로 관리하고자 한다면 두가지를 구현해줘야한다.
- operator==
- GetTypeHash()
왜냐하면 해당 값이 element 이기도 하지만, Key 이기도 하기 때문이다.
위 두 함수의 오버로드가 마땅하지 않다면, DefaultKeyFun 를 제공해주면 된다.
이에 대해선 이후에 다뤄보자.. (어렵네..)
(TMap 공부할때 본격적으로 다뤄보기로봄...)
📍실습 :
🔹실습 코드
#pragma region TArray
UE_LOG(LogTemp, Log, TEXT("==========================="));
TSet<int32> Int32Set;
for (int32 i = 1; i <= ArrayNum; ++i)
{
Int32Set.Add(i);
//Int32Set.Emplace(i);
}
Int32Set.Remove(2);
Int32Set.Remove(4);
Int32Set.Remove(6);
Int32Set.Remove(8);
Int32Set.Remove(10);
Int32Set.Add(2);
Int32Set.Add(4);
Int32Set.Add(6);
Int32Set.Add(8);
Int32Set.Add(10);
UE_LOG(LogTemp, Log, TEXT("==========================="));
#pragma endregion
Remove() 함수를 통해 2번째 엘리먼트를 제거한 경우 Set의 엘리먼트 개수가 9개라고 나온다.
하지만 실제 컨터이너를 보면 엘리먼트 공간은 10개로 유지되고 있고 제거된 값이 있던 위치는 "Invalid" 로 표기된다.
TArray 와 달리 Add 함수를 통해 추가된 엘리먼트가 뒤에서부터 들어간다.
TSet에 값이 추가될땐 가장 마지막(최근)에 요소가 빠진 빈틈부터 채워넣는 형태로 값이 추가된다.
때문에 이러한 특징을 고려하여 데이터를 순서를 예측하는건 사실상 어렵다.
TSet은 데이터가 무작위로 섞여있는 집합으로 다루는게 맞다.
🔹TArray 와 TSet
================================
개인 공부 기록용 포스팅입니다.
댓글, 질문 환영해요~!
Unreal Engine Ver : 5.1.1
Refer : Inflearn_이득우의 언리얼 프로그램
================================
'Unreal > Inflearn' 카테고리의 다른 글
Part1.10 언리얼 컨테이너 라이브러리 Ⅰ - Array (0) | 2024.04.11 |
---|---|
Part1.9 언리얼 C++ 설계 Ⅲ - 델리게이트(Delegate)⭕ (2) | 2024.04.11 |
Part1.8 언리얼 C++ 설계 Ⅱ - 컴포지션⭕ (0) | 2024.04.09 |
Part1.7 언리얼 C++ 설계 Ⅰ - 인터페이스⭕ (0) | 2024.04.09 |
Part1.6 언리얼 오브젝트 리플렉션 시스템 Ⅱ⭕ (0) | 2024.04.08 |