언리얼 - 자료구조 (TMap)

안영욱·2023년 2월 14일
0

언리얼

목록 보기
2/5

언리얼의 자료구조 TMap에대해 알아보겠습니다.
TArray와의 차이점을 언급하기에 TArray에 대한 정보도 어느정도 필요합니다.


TMapTArray(배열) 다음으로 가장 자주 사용되는 컨테이너이다.
TMap데이터를 키&값짝으로 (TPair<KeyType, ValueType>) 저장한다.
TMap을 저장 및 불러올 때는 키만 사용한다.

맵의 유형은 2가지로 TMapTMultiMap이있다.

  • TMap 키는 고유한 키를 사용한다.
  • TMultiMap다수의 동일한 키 저장을 지원한다.
  • 기존에 존재하는 키&값을 추가할때 TMap기존것을 대체하고, TMultiMap새로 추가한다.

1. TMap

TMap은 키와 값이 개별로 구분되어 정의됩니다.
TMap에서 엘리먼트(element)키&값 짝을 의미한다.
TMap에서 개별 컴포넌트키or값 둘중 한가지 를 의미한다.
TMap의 엘리먼트들(키&값)은 모두 같은 타입이어야 한다.
TMap의 경우 TArray와달리 배열순서를 보장받을수 없으며, 중간중간 비어있는 성긴배열형태를 띈다.


2. TMap을 생성하고 할당하기

2-2. 생성자

int32 타입의 , FString 타입의 을 사용하는 빈TMap을 생성

TMap<int32, FString> FruitMap;

2-3. Add

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: ""          } // 키는 있으나 값이없어 ""으로 저장됨
// ]

2-4. Emplace

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"    }
// ]

2-5. Append

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 은 비었습니다.

2-6. 에디터에서

TMapUPROPERTY 매크로와 편집가능 키워드 (EditAnywhere, EditDefaultsOnly, EditInstanceOnly) 중 하나를 마킹하면,
언리얼 에디터에서 엘리먼트를 추가 및 편집할수 있다.

UPROPERTY(Category = MapsAndSets, EditAnywhere)
TMap<int32, FString> FruitMap; // 이제 언리얼 에디터에서도 TMap을 편집할수 있다.

3. 반복처리

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")

3-1. CreateIterator, CreateConstIterators

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
        )
    );
}

4. 쿼리

4-1. NUM

TMap에 있는 엘리먼트의 개수를 반환한다.

int32 Count = FruitMap.Num();
// Count == 6

4-2. Contains

Contains함수는 TMap특정 키가 있는지 알아낼수 있다.

bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == false

4-3. [ ]

맵에 특정 키가 있다는 것을 안다면, 키를 인덱스로 하여 operator[]해당 값을 확인할수 있다.
non-const TMapnon-const 레퍼런스를, const TMapconst 레퍼런스를 반환한다.

FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8];
// 어서트! // null

4-4. Find

Find함수는 TMap에 키를 검색했을때 있다면 해당 값의 포인터를, 없다면 nullptr을 반환한다.

FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
//  Ptr8 == nullptr

4-5. FindOrAdd, FindRef

검색 결과의 유효성을 보장받아야할때 사용한다

FindOrAdd함수는 TMap해당 키가 존재하지 않을경우 해당 키에 기본값을 할당하여 TMap에 추가한다.

  • TMap을 수정할수 있기에 non const TMap에서만 사용가능하다.

FindRef함수는 TMap에 해당키가 없을경우 기본값의 사본을 반환한다.

  • FindOrAdd함수와 달리 추가하지 않기에 non constconst 모두 사용가능하다.
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이 확장됨에따라 포인터가 뒤바뀔수 있기에 이를 주의해아한다!

4-6. FindKey

FindKey함수는 을 사용하여 키를 찾아낼수있다 존재하지않을경우 null을 반환한다.

const int32* KeyMangoPtr   = FruitMap.FindKey(TEXT("Mango"));
const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
// *KeyMangoPtr   == 5
//  KeyKumquatPtr == nullptr
  • 을 검색하는것은 를 검색하는것보다 오래걸린다! TMap의 정렬기준은 이기때문이다.
    중복 값이 존재할경우 어떤 키를 반환하였는지는 알수없다.

4-7. GenerateKeyArray, GenerateValueArray

GenerateKeyArrayGenerateValueArray 함수는 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의 사본이 복사되어 할당됨

5. 제거

5-1. Remove

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"     }
// ]

5-2. FindAndRemoveChecked

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);
// 어서트!

5-3. RemoveAndCopyValue

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"  }
// ]

5-4. Empty, Reset

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 == []

6. 정렬(Sorting)

TMap은 소팅이 가능하지만 소팅 이후 반복처리를하면 순서를 보장받을수 없다.

6-1. KeySort, ValueSort

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" }
// ]

7. 연산자(operator)

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" }
// ]

7-1. MoveTemp

MoveTemp 함수를 이용해 TMap엘리먼트를 옮길수있다 기존에있던 엘리먼트는 사라지며, 옮겨진 TMap은 비워진다.

FruitMap = MoveTemp(NewMap);
// FruitMap == [
//  { Key: 4, Value: "Kiwi"  },
//  { Key: 5, Value: "Apple" },
//  { Key: 9, Value: "Melon" }
// ]
// NewMap == []

7-2. 슬랙(Slack)

Slack은 메모리는 할당되있으나 엘리먼트가 없는 경우를 말한다. (여유공간)

  • Reserve

    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을 파라미터로 호출하면 된다.
    이는 동일한 양의 엘리먼트를 삭제, 할당할때 유용하게 사용할수 있다.

7-3. Collapse , Shrink

CollapseShrink 함수로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함수는 끝부분 하나만 삭제시켰다.

7-4. Compact

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

profile
개발자좀 한번해보자

0개의 댓글