언리얼의 자료구조 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
에대해 공부하면서 정리하였습니다!
번역부분이 바로 이해가가지않는 부분은 제나름대로 수정해보았습니다.
수정할 부분이있다면 댓글로 알려주시면 감사합니다.