
언리얼의 자료구조 TMap에대해 알아보겠습니다.
TArray와의 차이점을 언급하기에 TArray에 대한 정보도 어느정도 필요합니다.
TMap은 TArray(배열) 다음으로 가장 자주 사용되는 컨테이너이다.
TMap은 데이터를 키&값짝으로 (TPair<KeyType, ValueType>) 저장한다.
TMap을 저장 및 불러올 때는 키만 사용한다.
맵의 유형은 2가지로 TMap 과 TMultiMap이있다.
TMap 키는 고유한 키를 사용한다.TMultiMap 은 다수의 동일한 키 저장을 지원한다.TMap은 기존것을 대체하고, TMultiMap은 새로 추가한다. TMap은 키와 값이 개별로 구분되어 정의됩니다.
TMap에서 엘리먼트(element) 는 키&값 짝을 의미한다.
TMap에서 개별 컴포넌트 는 키or값 둘중 한가지 를 의미한다.
TMap의 엘리먼트들(키&값)은 모두 같은 타입이어야 한다.
TMap의 경우 TArray와달리 배열순서를 보장받을수 없으며, 중간중간 비어있는 성긴배열형태를 띈다.
int32 타입의 키, FString 타입의 값을 사용하는 빈TMap을 생성
TMap<int32, FString> FruitMap;
Add함수를 사용하여 키&값을 TMap에 채울수 있다.
FruitMap.Add(5, TEXT("Banana")); FruitMap.Add(2, TEXT("Grapefruit")); FruitMap.Add(7, TEXT("Pineapple")); // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Grapefruit" }, // { Key: 7, Value: "Pineapple" } // ]
TMap은 중복 엘리먼트를 추가하는경우 엘리먼트가 갱신된다.
FruitMap.Add(2, TEXT("Pear")); // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Pear" }, // 키2의 Grapefruit를 갱신함 // { Key: 7, Value: "Pineapple" } // ]
Add함수에 키만 입력하고 값을 입력하지않을 경우 해당 키에 기본값(FString의경우 "")으로 TMap에 저장된다.
FruitMap.Add(4); // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "" } // 키는 있으나 값이없어 ""으로 저장됨 // ]
TArray 처럼 Add 대신 Emplace 를 사용해서 맵 삽입시의 임시 생성을 피할수 있다.
FruitMap.Emplace(3, TEXT("Orange")); // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "" }, // { Key: 3, Value: "Orange" } // ]
Append 함수를 사용하여 다른 TMap에서 모든 엘리먼트를 삽입시켜 병합시킬수 있다.
TMap<int32, FString> FruitMap2; //FruitMap2 생성후 Kiwi, Melon, Mango 를 추가 FruitMap2.Emplace(4, TEXT("Kiwi")); FruitMap2.Emplace(9, TEXT("Melon")); FruitMap2.Emplace(5, TEXT("Mango")); FruitMap.Append(FruitMap2); // FruitMap에 FruitMap2를 Append를 이용하여 병합! // FruitMap == [ // { Key: 5, Value: "Mango" }, // Mango에경우 기존에 존재하던 Banana를 갱신함 // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ] // 이제 FruitMap2 은 비었습니다.
TMap에 UPROPERTY 매크로와 편집가능 키워드 (EditAnywhere, EditDefaultsOnly, EditInstanceOnly) 중 하나를 마킹하면,
언리얼 에디터에서 엘리먼트를 추가 및 편집할수 있다.
UPROPERTY(Category = MapsAndSets, EditAnywhere) TMap<int32, FString> FruitMap; // 이제 언리얼 에디터에서도 TMap을 편집할수 있다.
TMap 에 대한 iteration(반복처리)는 TArray 와 유사하며 엘리먼트 유형이 TPair 임을 기억하고, C++ 의 범위 for기능 사용
for (auto& Elem : FruitMap) { FPlatformMisc::LocalPrint( *FString::Printf( TEXT("(%d, \"%s\")\n"), Elem.Key, *Elem.Value ) ); } // Output: // (5, "Mango") // (2, "Pear") // (7, "Pineapple") // (4, "Kiwi") // (3, "Orange") // (9, "Melon")
CreateIterator 함수는 읽기&편집이 가능한 반복자
CreateConstIterators 함수는 읽기전용 반복자
for (auto It = FruitMap.CreateConstIterator(); It; ++It) { FPlatformMisc::LocalPrint( *FString::Printf( TEXT("(%d, \"%s\")\n"), It.Key(), // same as It->Key *It.Value() // same as *It->Value ) ); }
TMap에 있는 엘리먼트의 개수를 반환한다.
int32 Count = FruitMap.Num(); // Count == 6
Contains함수는 TMap에 특정 키가 있는지 알아낼수 있다.
bool bHas7 = FruitMap.Contains(7); bool bHas8 = FruitMap.Contains(8); // bHas7 == true // bHas8 == false
맵에 특정 키가 있다는 것을 안다면, 키를 인덱스로 하여 operator[] 로 해당 값을 확인할수 있다.
non-const TMap은 non-const 레퍼런스를, const TMap은 const 레퍼런스를 반환한다.
FString Val7 = FruitMap[7]; // Val7 == "Pineapple" FString Val8 = FruitMap[8]; // 어서트! // null
Find함수는 TMap에 키를 검색했을때 있다면 해당 값의 포인터를, 없다면 nullptr을 반환한다.
FString* Ptr7 = FruitMap.Find(7); FString* Ptr8 = FruitMap.Find(8); // *Ptr7 == "Pineapple" // Ptr8 == nullptr
검색 결과의 유효성을 보장받아야할때 사용한다
FindOrAdd함수는 TMap에 해당 키가 존재하지 않을경우 해당 키에 기본값을 할당하여 TMap에 추가한다.
TMap을 수정할수 있기에 non const TMap에서만 사용가능하다.FindRef함수는 TMap에 해당키가 없을경우 기본값의 사본을 반환한다.
FindOrAdd함수와 달리 추가하지 않기에 non const 와 const 모두 사용가능하다. FString& Ref7 = FruitMap.FindOrAdd(7); // Ref7 == "Pineapple" // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ] FString& Ref8 = FruitMap.FindOrAdd(8); // Ref8 == "" // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" }, // { Key: 8, Value: "" } // 8을 추가함 // ]FString Val7 = FruitMap.FindRef(7); FString Val6 = FruitMap.FindRef(6); // Val7 == "Pineapple" // Val6 == "" // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" }, // { Key: 8, Value: "" } // ] // 6이존재하지않으나 추가하지않음
Find를 통해얻은 값이 FindOrAdd를 통해 덮어씌워지거나 TMap이 확장됨에따라 포인터가 뒤바뀔수 있기에 이를 주의해아한다!FindKey함수는 값을 사용하여 키를 찾아낼수있다 존재하지않을경우 null을 반환한다.
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango")); const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat")); // *KeyMangoPtr == 5 // KeyKumquatPtr == nullptr
TMap의 정렬기준은 키 이기때문이다.GenerateKeyArray 및 GenerateValueArray 함수는 TArray 를 각각 모든 키&값의 사본으로 채운다.
해당배열은 채워지기전 기존 엘리먼트들은 비워지기때문에, 엘리먼트 최종 수는 맵의 엘리먼트 수와 항상 같다.
TArray<int32> FruitKeys; TArray<FString> FruitValues; FruitKeys.Add(999); // 배열에 999를 추가 FruitKeys.Add(123); // 배열에 123을 추가 FruitMap.GenerateKeyArray (FruitKeys); FruitMap.GenerateValueArray(FruitValues); // FruitKeys == [ 5,2,7,4,3,9,8 ] // FruitValues == [ "Mango","Pear","Pineapple","Kiwi","Orange", // "Melon","" ] // 결과적으로 기존배열은 사라지고 TMap의 사본이 복사되어 할당됨
Remove함수에 제거할 키를 입력하여 TMap에서 제거할수있다.
반환값은 제거한 엘리먼트의 개수를 반환한다.
FruitMap.Remove(8); // // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ]
FindAndRemoveChecked함수는 키를 이용해 엘리먼트를 제거하고 제거한 값을 반환한다.
존재하지않는 키를 제거하려할경우 check함수를 통해 런타임 에러를 발생한다.
FString Removed7 = FruitMap.FindAndRemoveChecked(7); // Removed7 == "Pineapple" //제거한 값을 반환함 // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 2, Value: "Pear" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ] FString Removed8 = FruitMap.FindAndRemoveChecked(8); // 어서트!
RemoveAndCopyValue함수는 키의 존재여부를 true/false로 반환하며, 레퍼런스 파라미터로 제거한 값을 반환한다.
FString Removed; bool bFound2 = FruitMap.RemoveAndCopyValue(2, Removed); // 키 2제거를 시도하고 Removed에 값을 반환 // bFound2 == true // 키2는 존재하기에 true를 반환 // Removed == "Pear" // Removed에는 레퍼런스 파라미터를통해 제거된 값 "Pear"가 반환됨 // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ] bool bFound8 = FruitMap.RemoveAndCopyValue(8, Removed); //위와 형식은 동일 // bFound8 == false // 키 8은 존재하지않기에 false를 반환함 // Removed == "Pear", i.e. unchanged //키8은 존재하지않고 값이 없기에 위에서 키2를 제거할때 할당된 "Pear"가 유지됨 // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ]
Empty 또는 Reset함수를 이용해 TMap의 모든엘리먼트를 비울수 있다.
Empty의 경우 TMap에 남겨둘 슬랙의 양을 정할수 있다.Reset의 경우 기존 슬랙양을 유지한다.TMap<int32, FString> FruitMapCopy = FruitMap; // FruitMapCopy == [ // { Key: 5, Value: "Mango" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ] FruitMapCopy.Empty(); // 여기서 Reset() 을 호출해도 된다. // FruitMapCopy == []
TMap은 소팅이 가능하지만 소팅 이후 반복처리를하면 순서를 보장받을수 없다.
KeySort 혹은 ValueSort 함수는 키/값을 기준으로 정렬하게되며 두함수모두 이항술부로 정렬기준을 정할수있다.
FruitMap.KeySort([](int32 A, int32 B) { return A > B; // 키가 큰수 순서로 정렬 }); // FruitMap == [ // { Key: 9, Value: "Melon" }, // { Key: 5, Value: "Mango" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" } // ] FruitMap.ValueSort([](const FString& A, const FString& B) { return A.Len() < B.Len(); // 값 길이가 짧은 순서대로 }); // FruitMap == [ // { Key: 4, Value: "Kiwi" }, // { Key: 5, Value: "Mango" }, // { Key: 9, Value: "Melon" }, // { Key: 3, Value: "Orange" } // ]
TMap은 정규값 유형으로 복사 생성자, 할당 생성자를 사용할수 있다.
TMap<int32, FString> NewMap = FruitMap; // NewMap에 FruitMap을 할당하여도 복사하여 할당된다. NewMap[5] = "Apple"; NewMap.Remove(3); // FruitMap == [ // { Key: 4, Value: "Kiwi" }, // { Key: 5, Value: "Mango" }, // { Key: 9, Value: "Melon" }, // { Key: 3, Value: "Orange" } // ] // NewMap == [ // { Key: 4, Value: "Kiwi" }, // { Key: 5, Value: "Apple" }, // { Key: 9, Value: "Melon" } // ]
MoveTemp 함수를 이용해 TMap엘리먼트를 옮길수있다 기존에있던 엘리먼트는 사라지며, 옮겨진 TMap은 비워진다.
FruitMap = MoveTemp(NewMap); // FruitMap == [ // { Key: 4, Value: "Kiwi" }, // { Key: 5, Value: "Apple" }, // { Key: 9, Value: "Melon" } // ] // NewMap == []
Slack은 메모리는 할당되있으나 엘리먼트가 없는 경우를 말한다. (여유공간)
Reserve 함수를 사용해 엘리먼트없이 메모리를 할당시킬수 있다.FruitMap.Reserve(10); // 미리 10개의 엘리먼트가 들어올수 있느 메모리를 확보 for (int32 i = 0; i < 10; ++i) { FruitMap.Add(i, FString::Printf(TEXT("Fruit%d"), i)); } // FruitMap == [ // { Key: 9, Value: "Fruit9" }, // { Key: 8, Value: "Fruit8" }, // ... // { Key: 1, Value: "Fruit1" }, // { Key: 0, Value: "Fruit0" } // ]
Reset 혹은 Empty 사용시 0이아닌 Slack을 파라미터로 호출하면 된다.Collapse 및 Shrink 함수로Slack을 제거할수 있다.
Collapse의 경우 배열내의 모든 Slack을 제거한다, 이때 복사, 할당이 발생하여 속도면에서 느릴수있다.Shrink는 시작, 중간 부분의 컨테이너는 살리고 끝부분만 제거한다.for (int32 i = 0; i < 10; i += 2) { FruitMap.Remove(i); // 짝수 번째 엘리먼트만 삭제 } // FruitMap == [ // { Key: 9, Value: "Fruit9" }, // <invalid>, // { Key: 7, Value: "Fruit7" }, // <invalid>, // { Key: 5, Value: "Fruit5" }, // <invalid>, // { Key: 3, Value: "Fruit3" }, // <invalid>, // { Key: 1, Value: "Fruit1" }, // <invalid> // ] FruitMap.Shrink(); // 남아있는 슬랙을 제거 // FruitMap == [ // { Key: 9, Value: "Fruit9" }, // <invalid>, // { Key: 7, Value: "Fruit7" }, // <invalid>, // { Key: 5, Value: "Fruit5" }, // <invalid>, // { Key: 3, Value: "Fruit3" }, // <invalid>, // { Key: 1, Value: "Fruit1" } // ] // 가장 끝부분만 제거함
Shrink함수는 끝부분 하나만 삭제시켰다.Compact함수를 사용하면 슬랙을 끝부분으로 모두 모은다.
이상태에서 Shrink함수로 슬랙을 모두지울수있다.
FruitMap.Compact(); // FruitMap == [ // { Key: 9, Value: "Fruit9" }, // { Key: 7, Value: "Fruit7" }, // { Key: 5, Value: "Fruit5" }, // { Key: 3, Value: "Fruit3" }, // { Key: 1, Value: "Fruit1" }, // <invalid>, // <invalid>, // <invalid>, // <invalid> // ] // Compact를 통해 슬랙이 끝부분으로 모두 모임 FruitMap.Shrink(); // FruitMap == [ // { Key: 9, Value: "Fruit9" }, // { Key: 7, Value: "Fruit7" }, // { Key: 5, Value: "Fruit5" }, // { Key: 3, Value: "Fruit3" }, // { Key: 1, Value: "Fruit1" } // ] // Shrink가 끝부분의 슬랙을 모두지움!
출처
언리얼 공식홈페이지
TMap에대해 공부하면서 정리하였습니다!
번역부분이 바로 이해가가지않는 부분은 제나름대로 수정해보았습니다.
수정할 부분이있다면 댓글로 알려주시면 감사합니다.