UE4 C++实现任意类型数组蓝图排序节点(Quick Sort)
1. Overview
本教程旨在讲解在虚幻引擎(UE4) C++中实现蓝图(Blueprint)可调用的任意类型数组排序的蓝图节点。纯蓝图方法实现任意类型数组排序方法,请查看上一期 UE4 蓝图实现任意类型数组排序。以下泛型数组排序节点Array_Sort基于快速排序算法(Quick Sort)原理,冒泡排序(Bubble Sort)版见知乎链接:第2期 UE4 C++实现任意类型数组蓝图排序节点。编者水平有限,如有错漏敬请谅解。
2. Introduction
快速排序(Quick Sort)的平均时间复杂度为O(nlog2n),冒泡排序(Bubble Sort)的平均时间复杂度为O(n2),若以时间复杂度作为评价二种排序算法的优劣标准的话,快速排序明显优于冒泡排序。所以在已经介绍了冒泡版泛型数组排序方法,再次补充一个快速排序版泛型数组排序方法。同样地,以Sort by Function和Sort by Property作为比较数组成员大小的二种方式,分别编写了Sort Array by Function和Sort Array by Proerty二个泛型数组排序节点。
本文主要讲解UE4 C++泛型数组的快速排序蓝图节点的实现方法,以及该泛型快排节点与STL 排序方法的比较。
3. Sort Approach
3.1 基于SortBy Function,实现泛型数组排序
(1) h文件,Array_Sort函数声明:
/** Generic sort array by function using quick sort algorithm.
*
* @param Array Target array to sort
* @param Object The owner of CompareBy function,(DefaultToSelf)
* @param SortBy Function name of overloads operator "<" function
The signature of the comparison function should be equivalent to the following:
bool cmp(const Type1 &a, const Type2 &b); return value should named "ReturnValue"(bool)
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Sort Array by Function", CompactNodeTitle = "Sort", ArrayParm = "Array", AutoCreateRefTerm = "Array", DefaultToSelf = "Object", AdvancedDisplay = "Object"), Category = "Utilities|Array")
static void Array_SortV1(const TArray<int32>& Array, UObject* Object, FName SortBy);
// generic quick sort array by function name
static void GenericArray_SortByFunction(UObject* OwnerObject, UFunction* ComparedBy, void* TargetArray, UArrayProperty* ArrayProp);
// Low --> Starting index, High --> Ending index
static void QuickSortByFunction_Recursive(UObject* OwnerObject, UFunction* ComparedBy, UProperty* InnerProp, FScriptArrayHelper& ArrayHelper, int32 Low, int32 High);
// swapping items in place and partitioning the section of an array
static int32 QuickSortByFunction_Partition(UObject* OwnerObject, UFunction* ComparedBy, UProperty* InnerProp, FScriptArrayHelper& ArrayHelper, int32 Low, int32 High);
// Check that the CompareBy function is appropriate
static bool IsSuitableFunction(UFunction* ComparedBy, UArrayProperty* ArrayProp);
(2) h文件,Array_SortV1函数的自定义CustomThunk函数体
// sort array by function name
DECLARE_FUNCTION(execArray_SortV1)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<UArrayProperty>(NULL);
void* ArrayAAddr = Stack.MostRecentPropertyAddress;
UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_OBJECT(UObject, OwnerObject);
P_GET_PROPERTY(UNameProperty, SortBy);
if (!OwnerObject)
{
return;
}
UFunction* const SortFunction = OwnerObject->FindFunction(SortBy);
if ((!SortFunction || (SortFunction->NumParms != 3)))
{
return;
}
P_FINISH;
P_NATIVE_BEGIN;
GenericArray_SortByFunction(OwnerObject, SortFunction, ArrayAAddr, ArrayProperty);
P_NATIVE_END;
}
(3) cpp文件,GenericArray_SortByFunction()函数体
void UGenericArrayLibrary::GenericArray_SortByFunction(UObject* OwnerObject, UFunction* ComparedBy, void* TargetArray, UArrayProperty* ArrayProp)
{
if (!ComparedBy || !OwnerObject || !TargetArray)
{
return;
}
else if (!IsSuitableFunction(ComparedBy, ArrayProp))
{
return;
}
// Optimal
UKismetArrayLibrary::GenericArray_Shuffle(TargetArray, ArrayProp);
// Begin sort array
FScriptArrayHelper ArrayHelper(ArrayProp, TargetArray);
if (ArrayHelper.Num() < 2)
{
return;
}
else
{
UProperty* InnerProp = ArrayProp->Inner;
QuickSortByFunction_Recursive(OwnerObject, ComparedBy, InnerProp, ArrayHelper, 0, ArrayHelper.Num() - 1);
}
}
其中,快排算法优化:
// Optimal
UKismetArrayLibrary::GenericArray_Shuffle(TargetArray, ArrayProp);
(4) cpp文件,快排递归循环
void UGenericArrayLibrary::QuickSortByFunction_Recursive(UObject* OwnerObject, UFunction* ComparedBy, UProperty* InnerProp, FScriptArrayHelper& ArrayHelper, int32 Low, int32 High)
{
if (Low < High)
{
int32 Pivot = QuickSortByFunction_Partition(OwnerObject, ComparedBy, InnerProp, ArrayHelper, Low, High);
QuickSortByFunction_Recursive(OwnerObject, ComparedBy, InnerProp, ArrayHelper, Low, Pivot - 1);
QuickSortByFunction_Recursive(OwnerObject, ComparedBy, InnerProp, ArrayHelper, Pivot + 1, High);
}
}
(5) cpp文件,快排拆分
int32 UGenericArrayLibrary::QuickSortByFunction_Partition(UObject* OwnerObject, UFunction* ComparedBy, UProperty* InnerProp, FScriptArrayHelper& ArrayHelper, int32 Low, int32 High)
{
const int32 PropertySize = InnerProp->ElementSize * InnerProp->ArrayDim;
UBoolProperty* ReturnProp = Cast<UBoolProperty>(ComparedBy->GetReturnProperty());
// CompareBy function parameters address, 2 input parameter(array item) and 1 return parameter (bool)
uint8* FuncParamsAddr = (uint8*)FMemory::Malloc(ComparedBy->ParmsSize); /// note:ParamsSize = PropertySize* 2 +1
FMemory::Memzero(FuncParamsAddr, ComparedBy->ParmsSize);
/** Based on quick sort of stl */
InnerProp->CopyCompleteValueFromScriptVM(FuncParamsAddr, ArrayHelper.GetRawPtr(High)); //Params1: LastSmallElem = Array[High]
int32 i = Low - 1;
for (int32 j = Low; j < High; j++)
{
InnerProp->CopyCompleteValueFromScriptVM(FuncParamsAddr + PropertySize, ArrayHelper.GetRawPtr(j)); // Param2: Array[i]
OwnerObject->ProcessEvent(ComparedBy, FuncParamsAddr);
if (ReturnProp && ReturnProp->GetPropertyValue(FuncParamsAddr + PropertySize * 2))
{
i++;
ArrayHelper.SwapValues(i, j);
}
}
ArrayHelper.SwapValues(i + 1, High);
// release memory
FMemory::Free(FuncParamsAddr);
return i + 1;
}
(6) cpp文件,判断用于比较数组元素大小的蓝图函数是否合乎要求
bool UGenericArrayLibrary::IsSuitableFunction(UFunction* ComparedBy, UArrayProperty* ArrayProp)
{
// check CompareBy function's parameter number
if ((!ComparedBy || (ComparedBy->NumParms != 3)))
{
UE_LOG(LogTemp, Warning, TEXT("IsSuitableFunction -> CompareBy function should only have 3 parameters !"));
return false;
}
//Get return property of CompareBy function.
UBoolProperty* ReturnProp = Cast<UBoolProperty>(ComparedBy->GetReturnProperty());
if (!ReturnProp)
{
/// The return Property of max function must be bool and named "ReturnValue"
UE_LOG(LogTemp, Warning, TEXT("IsSuitableFunction -> Return value of CompareBy function should be bool type, and named ReturnValue."));
return false;
}
// Get all parameters of CompareBy function
TArray<UProperty*> ParamterList;
for (TFieldIterator<UProperty> It(ComparedBy); It; ++It)
{
UProperty* FuncParameter = *It;
ParamterList.Emplace(FuncParameter);
}
// Make sure the first/second input parameters of Compare function is same to array inner
if (!ParamterList[0]->SameType(ArrayProp->Inner) || !ParamterList[1]->SameType(ArrayProp->Inner))
{
/// The property of 1st input parameter of max function must be same as array member
UE_LOG(LogTemp, Warning, TEXT("IsSuitableFunction -> The parameters type of CompareBy should be same to array member."));
return false;
}
return true;
}
3.2 基于SortBy Property,实现泛型数组排序
(1) h文件,Array_Sort函数声明:
/** Generic sort array by property using quick sort algorithm.
*
* @param Array Target array to sort
* @param PropertyName Name is the variable to sort by for struct or object array. Otherwise, the parameter is ignored.
* @param bAscending If true, sort by ascending order.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Sort Array by Property", CompactNodeTitle = "Sort", ArrayParm = "Array", AutoCreateRefTerm = "Array", bAscending = "true"), Category = "Utilities|Array")
static void Array_SortV2(const TArray<int32>& Array, FName PropertyName, bool bAscending);
// generic quick sort array by property
static void GenericArray_SortV2(void* TargetArray, UArrayProperty* ArrayProp, FName PropertyName, bool bAscending);
// Low --> Starting index, High --> Ending index
static void QuickSort_RecursiveByProperty(FScriptArrayHelper& ArrayHelper, UProperty* InnerProp, UProperty* SortProp, int32 Low, int32 High, bool bAscending);
// swapping items in place and partitioning the section of an array
static int32 QuickSort_PartitionByProperty(FScriptArrayHelper& ArrayHelper, UProperty* InnerProp, UProperty* SortProp, int32 Low, int32 High, bool bAscending);
// generic compare two element of array by property
static bool GenericComparePropertyValue(FScriptArrayHelper& ArrayHelper, UProperty* InnerProp, UProperty* SortProp, int32 j, int32 High, bool bAscending);
(2) h文件,Array_SortV2函数的自定义CustomThunk函数体
// sort array by property
DECLARE_FUNCTION(execArray_SortV2)
{
Stack.MostRecentProperty = nullptr;
Stack.StepCompiledIn<UArrayProperty>(NULL);
void* ArrayAddr = Stack.MostRecentPropertyAddress;
UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);
if (!ArrayProperty)
{
Stack.bArrayContextFailed = true;
return;
}
P_GET_PROPERTY(UNameProperty, PropertyName);
P_GET_UBOOL(bAscending);
P_FINISH;
P_NATIVE_BEGIN;
GenericArray_SortV2(ArrayAddr, ArrayProperty, PropertyName, bAscending);
P_NATIVE_END;
}
(3) cpp文件,GenericArray_SortV2()函数体
void UGenericArrayLibrary::GenericArray_SortV2(void* TargetArray, UArrayProperty* ArrayProp, FName PropertyName, bool bAscending)
{
if (!TargetArray)
{
return;
}
// Optimal
UKismetArrayLibrary::GenericArray_Shuffle(TargetArray, ArrayProp);
FScriptArrayHelper ArrayHelper(ArrayProp, TargetArray);
UProperty* SortProperty = nullptr;
if (ArrayHelper.Num() < 2)
{
return;
}
else if (const UObjectProperty* ObjectProperty = Cast<const UObjectProperty>(ArrayProp->Inner))
{
SortProperty = FindField<UProperty>(ObjectProperty->PropertyClass, PropertyName);
}
else if (const UStructProperty* StructProperty = Cast<const UStructProperty>(ArrayProp->Inner))
{
SortProperty = SortAlgorithm::FindField<UProperty>(StructProperty->Struct, PropertyName);
}
else
{
SortProperty = ArrayProp->Inner;
}
if (SortProperty)
{
QuickSort_RecursiveByProperty(ArrayHelper, ArrayProp->Inner, SortProperty, 0, ArrayHelper.Num() - 1, bAscending);
}
}
(4) cpp文件,快排递归循环
void UGenericArrayLibrary::QuickSort_RecursiveByProperty(FScriptArrayHelper& ArrayHelper, UProperty* InnerProp, UProperty* SortProp, int32 Low, int32 High, bool bAscending)
{
if (Low < High)
{
int32 Pivot = QuickSort_PartitionByProperty(ArrayHelper, InnerProp, SortProp, Low, High, bAscending);
QuickSort_RecursiveByProperty(ArrayHelper, InnerProp, SortProp, Low, Pivot - 1, bAscending);
QuickSort_RecursiveByProperty(ArrayHelper, InnerProp, SortProp, Pivot + 1, High, bAscending);
}
}
(5) cpp文件,快排拆分
int32 UGenericArrayLibrary::QuickSort_PartitionByProperty(FScriptArrayHelper& ArrayHelper, UProperty* InnerProp, UProperty* SortProp, int32 Low, int32 High, bool bAscending)
{
int32 i = Low - 1;
for (int32 j = Low; j < High; j++)
{
if (GenericComparePropertyValue(ArrayHelper, InnerProp, SortProp, j, High, bAscending))
{
i++;
ArrayHelper.SwapValues(i, j);
}
}
ArrayHelper.SwapValues(i + 1, High);
return i + 1;
}
(6) cpp文件,比较数组中下标为j和High的成员大小
bool UGenericArrayLibrary::GenericComparePropertyValue(FScriptArrayHelper& ArrayHelper, UProperty* InnerProp, UProperty* SortProp, int32 j, int32 High, bool bAscending)
{
bool bResult = false;
void* LeftValueAddr = nullptr;
void* RightValueAddr = nullptr;
if (const UObjectProperty* ObjectProperty = Cast<const UObjectProperty>(InnerProp))
{
UObject* LeftObject = ObjectProperty->GetObjectPropertyValue(ArrayHelper.GetRawPtr(j));
UObject* RightObject = ObjectProperty->GetObjectPropertyValue(ArrayHelper.GetRawPtr(High));
LeftValueAddr = SortProp->ContainerPtrToValuePtr<void>(LeftObject);
RightValueAddr = SortProp->ContainerPtrToValuePtr<void>(RightObject);
}
else
{
LeftValueAddr = SortProp->ContainerPtrToValuePtr<void>(ArrayHelper.GetRawPtr(j));
RightValueAddr = SortProp->ContainerPtrToValuePtr<void>(ArrayHelper.GetRawPtr(High));
}
if (const UNumericProperty* NumericProp = Cast<const UNumericProperty>(SortProp))
{
if (NumericProp->IsFloatingPoint())
{
bResult = NumericProp->GetFloatingPointPropertyValue(LeftValueAddr) < NumericProp->GetFloatingPointPropertyValue(RightValueAddr);
}
else if (NumericProp->IsInteger())
{
bResult = NumericProp->GetSignedIntPropertyValue(LeftValueAddr) < NumericProp->GetSignedIntPropertyValue(RightValueAddr);
}
}
else if (const UBoolProperty* BoolProp = Cast<const UBoolProperty>(SortProp))
{
bResult = !BoolProp->GetPropertyValue(LeftValueAddr) && BoolProp->GetPropertyValue(RightValueAddr);
}
else if (const UNameProperty* NameProp = Cast<const UNameProperty>(SortProp))
{
bResult = NameProp->GetPropertyValue(LeftValueAddr).ToString() < NameProp->GetPropertyValue(RightValueAddr).ToString();
}
else if (const UStrProperty* StringProp = Cast<const UStrProperty>(SortProp))
{
bResult = (StringProp->GetPropertyValue(LeftValueAddr) < StringProp->GetPropertyValue(RightValueAddr));
}
else if (const UTextProperty* TextProp = Cast<const UTextProperty>(SortProp))
{
bResult = (TextProp->GetPropertyValue(LeftValueAddr).ToString() < TextProp->GetPropertyValue(RightValueAddr).ToString());
}
return bResult == bAscending;
}
(7)为了使Array_Sort节点适用BP UStruct (蓝图结构体)数组,将内置的FindField()函数做了部分修改。
namespace SortAlgorithm
{
template <class T> T* FindField(const UStruct* Owner, FName FieldName)
{
// We know that a "none" field won't exist in this Struct
if (FieldName.IsNone())
{
return nullptr;
}
// Search by comparing FNames (INTs), not strings
for (TFieldIterator<T>It(Owner); It; ++It)
{
if (It->GetFName() == FieldName)
{
return *It;
}
FName PropertyName = It->GetFName();
//PropertyName of USTRUCT(struct is defined in Blueprint) will contains invalid string,
//such as "Name_6_9093759148F93FCBBBF96AB8D348EC58".
if (PropertyName.ToString().Contains(FieldName.ToString(), ESearchCase::IgnoreCase, ESearchDir::FromStart))
{
return *It;
}
}
// If we didn't find it, return no field
return nullptr;
}
}
4. Usage
上述代码编译成功后,可在蓝图图表中找到以上二个SORT节点。
下面以float 数组为例,进行简单测试,以对比Sort by Propery和Sort by Function和Sort(STL)三种方式排序时间的差异;
随机获取一个浮点型数组,分别调用SORT节点,并打印排序时间
Print Duration宏蓝图代码如下
Sort by Function的SortBy函数应包含二个输入参数(变量类型与数组成员类型相同),以及一个名为ReturnValue(bool)的返回值,以上float数组的SortBy函数如下所示:
执行结果
Length | Quick Sorty by Function | Quick Sorty by Property | Sort by STL |
---|---|---|---|
100 | 0~1(ms) | 0(ms) | 0(ms) |
1000 | 12~16(ms) | 0~1(ms) | 0~1(ms) |
10000 | 164~182(ms) | 2~4(ms) | 0~1(ms) |
100000 | 2181~2365(ms) | 34~40(ms) | 2~7(ms) |
小结:
- 效率高低次序:STL > Sort by Propery >> Sort by Function;
- 当数组长度length<100时,三种排序方法在时间上没有明显差异,均可适用;当数length<10000时,使用Sort by Propery和Sort by STL二种进行排序没有明显差异。
- 由于Sort by Function排序方法使用了动态内存分配,并且调用了蓝图函数(SortBy),故效率最低;也正是因为使用蓝图函数比较大小,因此适用性更广,如二个以上排序变量的情形。
5. Conclusion
本文主要介绍了二种 UE4 泛型数组快速排序的蓝图节点的实现方法。经测试,该泛型蓝图排序节点性能虽然仍低于STL中的排序方法,但二者已处于同一层级。