언리얼의 자료구조 TSet
에대해 알아보겠습니다.
TArray``TMap
와 과 비교하기도 하며 TMap
에대해 어느정도 알필요가 있습니다.
TSet
은 TMap
과 TMultiMap
과 다르게 엘리먼트 자체를 키로 사용한다.
TSet
은 추가, 제거, 검색 기능이 굉장히 빠르다.
TSet
은 기본적으로 중복키를 사용할수 없다. (템플릿 파라미터로 사용할수는 있다)
TSet
은 순서가 중요하지않을때 사용할수있는 고속 컨테이너 자료형이다.
TSet
은 여러 템플릿 파라미터를 이용하여 작동방식을 변경할수도 있다.
DefaultKeyFuncs
에서 파생된 구조체는 해시 함수 기능을 제공하도록 지정할 수있고, 중복된 키가 존재하도록 할수있다.
TSet
다른 컨테이너 클래스와 마찬가지로, 데이터 저장을 위한 커스텀 메모리 할당자를 제공할 수 있다.
TSet
은 해시를 사용하며 KeyFuncs
템플릿 파라미터가 제공된 경우 대표적으로 다음과 같은 항목들 을 지정할수 있다.
TSet
은 이러한 기능을 설정하지않아도 자동적으로 적절한 방식으로 처리된다.
TSet
는 메모리 할당방식을 수정할수 있다 하지만 FHeapAllocator
, TInlineAllocator
등 언리얼 표준 할당자는 사용할수 없다.
대신 해시 버켓수, 엘리먼트 저장을 따르는 표준 할당자를 지정할수 있다.
TSet
은 TArray
와달리 순서를 보장받을수 없으며 메모리또한 인접하지 않을수 있다.
TSet
의 데이터 구조는 희소배열로 엘리먼트 사이 간극을 효율적으로 사용할수 있다,
엘리먼트가 지워지면 이앞 간극을 앞으로 추가되는 엘리먼트가 채우기때문이다.
TSet<FString> FruitSet; // FString을
FString
타입을 저장하는 TSet
을 생성, 초기화를 시켜주지않아 할당된 데이터는 없다.Add
함수를 사용해 TSet
에 데이터를 저장할수 있다.
FruitSet.Add(TEXT("Banana")); FruitSet.Add(TEXT("Grapefruit")); FruitSet.Add(TEXT("Pineapple")); // FruitSet == [ "Banana", "Grapefruit", "Pineapple" ]
기본 할당자를 사용하여 추가하였기에 키는 고유성을 보장받을수 있다, 이때 중복된 키를 저장하려고할때 발생하는 현상 ▽
FruitSet.Add(TEXT("Pear")); FruitSet.Add(TEXT("Banana")); // FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear" ] // Note: banana엘리먼트는 하나만 존재할수 있다.
Pear
는 추가되었지만 Banana
는 기존 엘리먼트를 갱신하여 할당되어 따로 추가되지않는다.TArray
와 마찬가지로 Emplace
함수를 사용하여 임시생성을 피할수있다.
FruitSet.Emplace(TEXT("Orange")); // 결과는 같으나 복사하여 대입하는 임시생성 과정을 거치지않는다. // FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange" ]
TSet
은 인수가 하나인 생성자로만 Emplace
함수를 사용할수 있다.Append
함수를 사용하여 TSet
를 서로 병합할수 있다.
TSet<FString> FruitSet2; FruitSet2.Emplace(TEXT("Kiwi")); FruitSet2.Emplace(TEXT("Melon")); FruitSet2.Emplace(TEXT("Mango")); FruitSet2.Emplace(TEXT("Orange")); FruitSet.Append(FruitSet2); // FruitSet에 FruitSet2를 병합시킴 // FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange", "Kiwi", "Melon", "Mango" ]
TSet
에 UPROPERTY()
매크로와 편집가능 키워드 (EditAnywhere
, EditDefaultsOnly
, EditInstanceOnly
) 중 하나를 마킹하면, 언리얼 에디터에서 엘리먼트를 추가 및 편집 할 수 있습니다.
UPROPERTY(Category = SetExample, EditAnywhere) // UPROPERTY를 이용하여 에디터에서 수정이 가능하다. TSet<FString> FruitSet;
TSet
의 반복자는 TArray
와 마찬가지로 C++의 범위 for문을 사용하면 된다.
for (auto& Elem : FruitSet) { FPlatformMisc::LocalPrint( *FString::Printf( TEXT(" \"%s\"\n"), *Elem ) ); } // Output: // "Banana" // "Grapefruit" // "Pineapple" // "Pear" // "Orange" // "Kiwi" // "Melon" // "Mango"
CreateIterator
및 CreateConstIterators
함수로 반복자를 만들수 있다.
CreateIterator
는 읽기 - 쓰기가 가능한 반환자CreateConstIterators
는 읽기만 가능한 반환자for (auto It = FruitSet.CreateConstIterator(); It; ++It) { FPlatformMisc::LocalPrint( *FString::Printf( TEXT("(%s)\n"), *It ) ); }
Num
함수로 TSet
에 엘리먼트가 얼마나 저장되어있는지 확인할수 있다.
int32 Count = FruitSet.Num(); // Count == 8
Contains
함수로 내가 지정한 값이 TSet
에 존재하는지 확인할수 있다.
bool bHasBanana = FruitSet.Contains(TEXT("Banana")); bool bHasLemon = FruitSet.Contains(TEXT("Lemon")); // bHasBanana == true //banana가 존재하기때문에 true로 반환 // bHasLemon == false //Lemon이 존재하지않아 false로 반환
FSetElementId
구조체로 내가 지정한 키 인덱스를 담을수있다, []
와 함께사용하여 해당 인덱스에 접근해 엘리먼트를 가져올수있다.
FSetElementId BananaIndex = FruitSet.Index(TEXT("Banana")); // BananaIndex is a value between 0 and (FruitSet.Num() - 1) FPlatformMisc::LocalPrint( *FString::Printf( TEXT(" \"%s\"\n"), *FruitSet[BananaIndex] ) ); // Prints "Banana"
FSetElementId LemonIndex = FruitSet.Index(TEXT("Lemon")); // LemonIndex is INDEX_NONE (-1) FPlatformMisc::LocalPrint( *FString::Printf( TEXT(" \"%s\"\n"), *FruitSet[LemonIndex] ) ); // Assert!
Find
함수를 사용하여 찾고자하는 키가 있다면 해당 엘리먼트의 pointer
를, 없다면 nullpointer
를 반환한다.
FString* PtrBanana = FruitSet.Find(TEXT("Banana")); FString* PtrLemon = FruitSet.Find(TEXT("Lemon")); // *PtrBanana == "Banana" // PtrLemon == nullptr
array
함수는 TSet
의 모든 엘리먼트로 채워진 TArray
로 반환한다.
TArray<FString> FruitArray = FruitSet.Array(); // FruitArray == [ "Banana","Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ] (order may vary)
Remove
함수를 이용하여 엘리먼트를 제거할수있다, 하지만 이는 엘리먼트에 대한 반복처리도중에 사용되길 권장한다.
Remove
함수는 제거한 엘리먼트 수를 반환한다.
FruitSet.Remove(0); //인덱스 번호로 제거를 할경우 // FruitSet == [ "Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ] // 0번 인덱스의 엘리먼트 제거
int32 RemovedAmountPineapple = FruitSet.Remove(TEXT("Pineapple")); // 엘리먼트로 제거를 할경우 // RemovedAmountPineapple == 1 // 제거한 엘리먼트가 1개이기때문에 1을 반환 // FruitSet == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]
FString RemovedAmountLemon = FruitSet.Remove(TEXT("Lemon")); // RemovedAmountLemon == 0 // Lemon은 존재하지않는 엘리먼트이기에 아무것도 지워지지않고 0을 반환
Empty
또는 Reset
함수를 이용해 TSet
의 모든엘리먼트를 비울수 있다.
TSet<FString> FruitSetCopy = FruitSet; // FruitSetCopy == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]
FruitSetCopy.Empty(); // FruitSetCopy == []
Empty
의경우 TSet
에 남겨둘 슬랙양을 정할수있다.Reset
의경우 기존의 슬랙양을 유지한다.TSet
은 정렬할수 있다, 하지만 정렬이후 배열이 수정된다면 더이상 그순서를 보장할순 없다.
기본적으로 TSet
은 순서가 크게 상관없는경우 사용하기때문에 특별한 경우를 제외하곤 정렬할 이유가없다.
sort
함수는 이항술부를 받아 정렬기준을 정할수 있다.
FruitSet.Sort([](const FString& A, const FString& B) { return A > B; // 알파벳 역순으로 정렬 }); // FruitSet == [ "Pear", "Orange", "Melon", "Mango", "Kiwi", "Grapefruit" ] (order is temporarily guaranteed)
FruitSet.Sort([](const FString& A, const FString& B) { return A.Len() < B.Len(); // 길이가 짧은 순서로 정렬 }); // FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ] (order is temporarily guaranteed)
TSet
은 TArray
처럼 값을 복사 생성연산자, 할당 연산자를 통해 복사할수 있다.
TSet<int32, FString> NewSet = FruitSet; // = 연산자를 이용해 값을 복사, 초기화시킴 NewSet.Add(TEXT("Apple")); NewSet.Remove(TEXT("Pear")); // FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ] // NewSet == [ "Kiwi", "Melon", "Mango", "Orange", "Grapefruit", "Apple" ]
Slack
은 메모리는 할당되었지만 엘리먼트는 없는 여유 공간을 의미한다.
Reserve
함수를 사용하여 엘리먼트 없이 메모리는 할당할수 있다.
FruitSet.Reserve(10); // Reserve 함수로 먼저 길이가 10인 TSet을 선언해둠 for (int32 i = 0; i < 10; ++i) { FruitSet.Add(FString::Printf(TEXT("Fruit%d"), i)); } // FruitSet == [ "Fruit9", "Fruit8", "Fruit7" ... "Fruit2", "Fruit1", "Fruit0" ]
TSet
에서 모든 슬랙을 제거하려면 Collpase
또는 Shrink
를 사용하면된다.
Collpase
함수는 배열내의 모든 Slack
을 제거한다, 이때 불필요한 복사, 할당과정이있어 속도가 느릴수있다.Shrink
함수는 배열의 끝부분의 모든 Slack
을 제거한다,중간에 있는 슬랙은 제거하지 않는다.// 세트에서 엘리먼트를 하나 건너 하나씩 제거합니다. 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" ] // 이때 제거된 슬랙은 배열 가장끝에있는 슬랙 하나뿐이다
Compact
혹은 CompactStable
함수는 배열내의 존재하는 Slack
을 배열 끝으로 모두 모아준다, 이를 이용해 Compact
함수이후 Shrink
를 이용해 모든 슬랙을 제거할수 있다.
Compact
함수는 모든 슬랙을 배열 끝으로 모아준다, 이때 배열내 데이터들의 순서는 고려하지않는다.CompactStable
함수는 배열내 데이터들의 순서를 고려하고 모든 슬랙을 배열끝으로 모아준다.FruitSet.CompactStable(); // 모든 슬랙을 뒤쪽으로 모아줌 이때 데이터의 순서를 고려함 // FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0", <invalid>, <invalid>, <invalid>, <invalid> ] FruitSet.Shrink(); // 모든 슬랙이 제거됨 // FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0" ]
언리얼 공식 홈페이지
언리얼 자료구조 TSet
에대해 작성해보았습니다
번역부분이 바로 이해가가지않는 부분은 제나름대로 수정해보았습니다.