언리얼 C++ 프로그래밍

[언리얼 C++] 컨테이너 클래스 TArray 사용법 (소팅, 쿼리)

언린이 2021. 7. 6. 23:11

TArray 컨테이너 클래스의 기본적인 설명과 간단한 사용법은 [언리얼 C++] 컨테이너 클래스 TArray 사용법 (생성 및 삽입, 반복처리) (tistory.com) 글을 참고하시기 바랍니다.

이 글에서는 TArray 컨테이너 클래스의 소팅, 쿼리 기능에 대해 알아보겠습니다.

 

 

1. TArray 컨테이너 소팅 기능

StrArray.Sort(); // StrArray = {"!","Brave","Hello","of","Tomorrow","World"}

TArray 컨테이너는 Sort() 함수를 호출하는 것만으로도 간단히 소팅이 가능합니다.

 

StrArray.Sort([](const FString& A, const FString& B) {
    return A.Len() < B.Len();
});
// StrArray = {"!","of","Hello","Brave","World","Tomorrow"}

그리고 연산자 <를 사용해서 엘리먼트 값을 원하는 조건대로 소팅할 수도 있습니다. FString의 경우 대소문자 구분 없이 사전식 비교를 합니다.

위 예제 코드는 엘리먼트의 길이가 짧은 순으로 소팅한 것입니다.

참고로 길이가 같은 "Hello", "Brave", "World" 엘리먼트들의 상대 순서가 기존에 비해 바뀐 것을 볼 수 있습니다. 왜냐하면 Sort() 함수는 길이가 같은 엘리먼트의 상대 순서를 동일하게 보장하지 않는 비안정적인 함수이기 때문입니다. Sort() 함수는 일종의 간단 소팅 기능으로 구현되어 있습니다.

 

StrArray.HeapSort([](const FString& A, const FString& B) {
    return A.Len() < B.Len();
});
// StrArray = {"!","of","Hello","Brave","World","Tomorrow"}

HeapSort() 함수는 이진 술부가 존재하든 존재하지 않든, 힙 소팅을 수행합니다.

Sort() 함수처럼 HeapSort() 함수 또한 안정적이지 못합니다.

 

StrArray.StableSort([](const FString& A, const FString& B) {
    return A.Len() < B.Len();
});
// StrArray = {"!","of","Brave","Hello","World","Tomorrow"}

마지막으로 StableSort() 함수는 소팅 이후 동등한 엘리먼트의 상대 순서를 유지시킵니다.

즉, "Brave", "Hello", "World"의 기존 상대 순서를 유지시킨다는 얘기입니다.

StableSort() 함수는 병합 소트로 구현되어 있습니다.

 

 

2. TArray 컨테이너 쿼리 기능

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

TArray 컨테이너는 Num() 함수를 사용해서 컨테이너에 엘리먼트가 몇 개인지 확인할 수 있습니다.

 

FString* StrPtr = StrArray.GetData();
// StrPtr[0] = "!"
// StrPtr[1] = "of"
// ...
// StrPtr[5] = "Tomorrow"

그리고 GetData() 함수를 사용해서 컨테이너 내 엘리먼트에 대한 포인터를 반환시킬 수 있습니다. 이 포인터는 컨테이너가 존재하는 한에서, 그리고 컨테이너에 대한 변형 연산이 적용되기 전에만 유효합니다.

컨테이너가 const인 경우, 반환되는 포인터 역시 const입니다.

 

uint32 ElementSize = StrArray.GetTypeSize(); // ElementSize = sizeof(FString)

컨테이너 엘리먼트의 사이즈에 대해서도 물어볼 수 있습니다.

 

FString Elem1 = StrArray[1]; // Elem1 = "of"

엘리먼트 값을 얻으려면, operator[] 인덱싱을 사용해서 원하는 엘리먼트에 대한 시작 인덱스 값을 전해주면 됩니다.

 

bool bValidM1 = StrArray.IsValidIndex(-1); // bValidM1 == false
bool bValid0  = StrArray.IsValidIndex(0); // bValid0  == true
bool bValid5  = StrArray.IsValidIndex(5); / bValid5  == true
bool bValid6  = StrArray.IsValidIndex(6); // bValid6  == false, 인덱스 초과

유효하지 않은 인덱스, 즉 0미만이나 Num() 이상 값을 전해주면, 런타임 오류가 발생합니다.

컨테이너의 특정 인덱스가 유효한지 IsValidIndex() 함수를 통해 물어볼 수 있습니다.

 

StrArray[3] = StrArray[3].ToUpper(); // StrArray = {"!","of","Brave","HELLO","World","Tomorrow"}

operator[]는 엘리먼트에 대한 레퍼런스를 반환하므로, 컨테이너가 const가 아니라는 가정하에 컨테이너 내 엘리먼트들을 변형시키는데 사용할 수 있습니다.

GetData() 함수처럼 operator[]도 컨테이너가 const인 경우 const 레퍼런스를 반환합니다.

 

FString ElemEnd  = StrArray.Last(); // ElemEnd  = "Tomorrow"
FString ElemEnd0 = StrArray.Last(0); // ElemEnd0 = "Tomorrow"
FString ElemEnd1 = StrArray.Last(1); // ElemEnd1 = "World"
FString ElemTop  = StrArray.Top(); // ElemTop  = "Tomorrow"

Last() 함수를 사용하여 컨테이너 끝에서부터 역순으로 인덱싱하여 값을 가져올 수 있습니다.

인덱스 기본값은 0입니다. Top() 함수는 Last(0) 함수와 동일한 처리를 하며, 인덱스를 받지 않는다는 차이점이 있습니다.

 

bool bHello   = StrArray.Contains(TEXT("Hello")); // bHello = true
bool bGoodbye = StrArray.Contains(TEXT("Goodbye")); // bGoodbye = false

컨테이너에 특정 엘리먼트가 존재하는지도 물어볼 수 있습니다.

 

bool bLen5 = StrArray.ContainsByPredicate([](const FString& Str){
    return Str.Len() == 5;
});
bool bLen6 = StrArray.ContainsByPredicate([](const FString& Str){
    return Str.Len() == 6;
});
// bLen5 = true
// bLen6 = false

또한, 컨테이너에 지정된 술부와 일치하는 엘리먼트가 존재하는지도 물어볼 수 있습니다.

 

int32 Index;
if (StrArray.Find(TEXT("Hello"), Index))
{
    // Index = 3
}

Find() 함수를 사용하여 엘리먼트를 찾을 수 있습니다. 엘리먼트가 존재하는지 검사하고 존재할때 해당 엘리먼트의 인덱스 값을 받아오고 싶다면 Find() 함수를 사용하면 됩니다.

 

int32 IndexLast;
if (StrArray.FindLast(TEXT("Hello"), IndexLast))
{
    // IndexLast = 3
}

중복된 엘리먼트가 있는 상태에서 맨 마지막 엘리먼트의 인덱스를 찾고자 하는 경우, FindLast() 함수를 사용하면 됩니다.

Find(), FindLast() 함수 모두 엘리먼트를 찾았는지 여부를 나타내는 bool 값을 반환하고 찾았을 경우 파라미터로 넘겨준 변수에 인덱스 값을 저장합니다.

 

int32 Index2 = StrArray.Find(TEXT("Hello")); // Index2 = 3
int32 IndexLast2 = StrArray.FindLast(TEXT("Hello")); // IndexLast2 = 3
int32 IndexNone  = StrArray.Find(TEXT("None")); // IndexNone = INDEX_NONE

Find(), FindLast() 함수는 엘리먼트 인덱스를 직접 반환할 수도 있습니다. 인덱스를 파라미터로 넘겨주지 않으면 이런식으로 작동합니다. 인덱스를 저장할 변수를 넘겨주는 것보다 간결할 수 있습니다.

엘리먼트를 찾지 못했으면, INDEX_NONE 값이 반환됩니다.

 

int32 Index = StrArray.IndexOfByKey(TEXT("Hello")); // Index = 3

IndexOfByKey() 함수 operator==(ElementType, KeyType)이 존재하는 키 유형에 대해 작동합니다. IndexOfByKey() 함수는 처음 찾은 엘리먼트의 인덱스를 반환하거나 찾은 것이 없으면 INDEX_NONE 값을 반환합니다.

 

int32 Index = StrArray.IndexOfByPredicate([](const FString& Str){
    return Str.Contains(TEXT("r"));
});
// Index = 2

IndexOfByPredicate() 함수는 지정된 술부에 일치하는 첫 엘리먼트 인덱스를 찾는데 사용할 수 있으며, 찾은 것이 없으면 마찬가지로 INDEX_NONE 값을 반환합니다.

 

auto* OfPtr  = StrArray.FindByKey(TEXT("of"))); // OfPtr = &StrArray[1]
auto* ThePtr = StrArray.FindByKey(TEXT("the"))); // ThePtr = nullptr

인덱스 반환 대신, 찾은 엘리먼트로의 포인터를 반환할 수도 있습니다. FindByKey() 함수는 엘리먼트를 임의 오브젝트에 비교하는 식으로 IndexOfByKey() 함수처럼 작동하나, 찾은 엘리먼트가 있으면 그 포인터를, 없으면 nullptr을 반환합니다.

 

auto* Len5Ptr = StrArray.FindByPredicate([](const FString& Str){
    return Str.Len() == 5;
});
auto* Len6Ptr = StrArray.FindByPredicate([](const FString& Str){
    return Str.Len() == 6;
});
// Len5Ptr = &StrArray[2]
// Len6Ptr = nullptr

마찬가지로 FindByPredicate() 함수 역시 IndexOfByPredicate() 함수처럼 사용되지만, 인덱스의 포인터를 반환한다는 점이 다릅니다.

 

auto Filter = StrArray.FilterByPredicate([](const FString& Str){
    return !Str.IsEmpty() && Str[0] < TEXT('M');
});

마지막으로, FilterByPredicate() 함수를 통해 특정 술부에 일치하는 엘리먼트의 컨테이너를 가져올 수 있습니다.