KoreanFoodie's Study

언리얼에서 TMap 에 Compare Functor 지정하기 본문

Game Dev/Unreal C++ : Dev Log

언리얼에서 TMap 에 Compare Functor 지정하기

GoldGiver 2023. 6. 5. 18:05

언리얼에서 TMap 에 Compare Functor 지정하기

핵심 :

1. C++ 에서는 Functor class 를 넣어 주기만 하면 된다.
2. TMap 에서는 TMap 자체가 그냥 unordered_map 이라 그런 건 없다. 만약 Predicate 를 지정하고 싶으면, TSortedMap 을 사용하자!
3. TMap 을 정렬할 때는 Sort 를 사용하자... 후...

C++ 에서는 TMap 에서 Key 값을 이용한 정렬을 역순으로 하고 싶을 때, std::less 나 std::greater 같은 predicate 클래스를 사용했다.
 
혹은 아래와 같이 Functor class 를 만들어 3번째 인자에 넣어 주기만 하면 되었는데...

#include <iostream>
#include <map>
#include <string>

class MyCompare {
public:
  bool operator()(const std::string& a, const std::string& b) const {
    // Compare strings in reverse order
    return a > b;
  }
};

int main() {
  std::map<std::string, int, MyCompare> myMap;

  myMap["one"] = 1;
  myMap["two"] = 2;
  myMap["three"] = 3;

  for (auto& p : myMap) {
    std::cout << p.first << ": " << p.second << std::endl;
  }

  return 0;
}

언리얼 TMap 의 경우, 조금 복잡하다.
뭐 사실 크게 달라진 것은 없는데, 인자도 하나 추가되었고, 만족해야 하는 조건이 조금 추가된 정도이다.
 
바로 본론으로 가서, 이를 적용하는 코드는 아래와 같다 :

// 템플릿 타입 설명 :
// 첫번째 MyKeyType 은 키값의 타입을 넣는다. int 같은거. 
// 두번째의 FMyValueType 은 값의 타입.
// 세 번째 인자는 TMap 의 경우 false, TMultiMap 의 경우 true 이다. bInAllowDuplicateKeys 인데, 자세한건 검색...

template <typename KeyType, typename ValueType, bool bInAllowDuplicateKeys>
struct FMyKeyComparator : TDefaultMapHashableKeyFuncs<KeyType, ValueType, bInAllowDuplicateKeys>
{
	bool operator()(const KeyType& A, const KeyType& B) const
	{
		// Custom comparison logic here
		return A > B;
	}
};

using TCustomMapType = TMap<MyKeyType, TSharedPtr<FMyValueType>, FDefaultSetAllocator, 
    FMyKeyComparator<MyKeyType, TSharedPtr<FMyValueType>, false>>;


/* */

// 편-안
TCustomMapType myMap;

// 만약 KeyType 에 int 같은 Primitive Type 이외에 직접 정의한 녀석을 넣는다면,
// 따로 GetTypeHash 를 Override 해야 할 것으로 보인다. Primitive Type 들은 GetTypeHash 가 정의되어 있을 듯.

위 코드를 적용하면 원하는 대로 Functor 가 들어간 TMap 을 만들 수 있을 것이다 😉
(물론 보통 TMap 의 정렬이 필요할 때는 그냥 sort 를 써 주어야 하는 것으로 보인다.)
 
하지만... 실제로 정렬은 되지 않는다. 왜냐면, TMap 은 unordered_map 같은 녀석이라, operator() 를 삽입/삭제 시에 호출하지는 않기 때문이다....
 
대안으로, TSortedMap 을 사용하여 우리가 원했던 Predicate 를 Functor 로 넣을 수도 있다 :

/** 
 * A Map of keys to value, implemented as a sorted TArray of TPairs.
 *
 * It has a mostly identical interface to TMap and is designed as a drop in replacement. Keys must be unique,
 * there is no equivalent sorted version of TMultiMap. It uses half as much memory as TMap, but adding and 
 * removing elements is O(n), and finding is O(Log n). In practice it is faster than TMap for low element
 * counts, and slower as n increases, This map is always kept sorted by the key type so cannot be sorted manually.
 */
template <typename KeyType, typename ValueType, typename ArrayAllocator /*= FDefaultAllocator*/, typename SortPredicate /*= TLess<KeyType>*/ >
class TSortedMap
/* ... */

하지만 TSortedMap 은 조금 느린 듯 하다. 삽입/삭제가 O(n) 이라니? 탐색이 O(Log n) 이다. 🤨
그래서.. 그냥 TMap 을 쓰고, 필요할 때 Sort 를 쓰는게 좋을 것 같다.
 
TMap 에서 Sort Predicate 쪽이 궁금하면 '더보기'를 참고하자. 안되는 이유를 알 수 있다...

더보기
public:
	/**
	 * Sorts the pairs array using each pair's Key as the sort criteria, then rebuilds the map's hash.
	 * Invoked using "MyMapVar.KeySort( PREDICATE_CLASS() );"
	 */
	template<typename PREDICATE_CLASS>
	FORCEINLINE void KeySort(const PREDICATE_CLASS& Predicate)
	{
		Super::Pairs.Sort(FKeyComparisonClass<PREDICATE_CLASS>(Predicate));
	}

	/**
	 * Stable sorts the pairs array using each pair's Key as the sort criteria, then rebuilds the map's hash.
	 * Invoked using "MyMapVar.KeySort( PREDICATE_CLASS() );"
	 */
	template<typename PREDICATE_CLASS>
	FORCEINLINE void KeyStableSort(const PREDICATE_CLASS& Predicate)
	{
		Super::Pairs.StableSort(FKeyComparisonClass<PREDICATE_CLASS>(Predicate));
	}

	/**
	 * Sorts the pairs array using each pair's Value as the sort criteria, then rebuilds the map's hash.
	 * Invoked using "MyMapVar.ValueSort( PREDICATE_CLASS() );"
	 */
	template<typename PREDICATE_CLASS>
	FORCEINLINE void ValueSort(const PREDICATE_CLASS& Predicate)
	{
		Super::Pairs.Sort(FValueComparisonClass<PREDICATE_CLASS>(Predicate));
	}

	/**
	 * Stable sorts the pairs array using each pair's Value as the sort criteria, then rebuilds the map's hash.
	 * Invoked using "MyMapVar.ValueSort( PREDICATE_CLASS() );"
	 */
	template<typename PREDICATE_CLASS>
	FORCEINLINE void ValueStableSort(const PREDICATE_CLASS& Predicate)
	{
		Super::Pairs.StableSort(FValueComparisonClass<PREDICATE_CLASS>(Predicate));
	}


private:

	/** Extracts the pair's key from the map's pair structure and passes it to the user provided comparison class. */
	template<typename PREDICATE_CLASS>
	class FKeyComparisonClass
	{
		TDereferenceWrapper< KeyType, PREDICATE_CLASS> Predicate;

	public:

		FORCEINLINE FKeyComparisonClass(const PREDICATE_CLASS& InPredicate)
			: Predicate(InPredicate)
		{}

		FORCEINLINE bool operator()(const typename Super::ElementType& A, const typename Super::ElementType& B) const
		{
			return Predicate(A.Key, B.Key);
		}
	};

	/** Extracts the pair's value from the map's pair structure and passes it to the user provided comparison class. */
	template<typename PREDICATE_CLASS>
	class FValueComparisonClass
	{
		TDereferenceWrapper< ValueType, PREDICATE_CLASS> Predicate;

	public:

		FORCEINLINE FValueComparisonClass(const PREDICATE_CLASS& InPredicate)
			: Predicate(InPredicate)
		{}

		FORCEINLINE bool operator()(const typename Super::ElementType& A, const typename Super::ElementType& B) const
		{
			return Predicate(A.Value, B.Value);
		}
	};

 
TMap 의 정렬을 위해 Sort 를 해보자. 예제는 공식 문서에도 잘 나와 있다 😅

FruitMap.KeySort([](int32 A, int32 B) {
    return A > B; // sort keys in reverse
});
// 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(); // sort strings by length
});
// FruitMap == [
//  { Key: 4, Value: "Kiwi"   },
//  { Key: 5, Value: "Mango"  },
//  { Key: 9, Value: "Melon"  },
//  { Key: 3, Value: "Orange" }
// ]

TMap Sort... TMap 정렬은 위처럼 하자.

 

참고 : 언리얼 공식 문서  - TMap
Comments