From 25d6308a5f5df0bca82819951cdb93973c2335ed Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Sat, 11 Nov 2017 20:08:43 +1100 Subject: [PATCH] List removal for arrays, template specialize sort --- dqn.h | 253 +++++++++++++++++++--- dqn_unit_test.cpp | 540 +++++++++++++++++++++++++++++++--------------- 2 files changed, 589 insertions(+), 204 deletions(-) diff --git a/dqn.h b/dqn.h index 5366be4..83163fb 100644 --- a/dqn.h +++ b/dqn.h @@ -474,6 +474,9 @@ struct DqnString //////////////////////////////////////////////////////////////////////////////// // #DqnArray Public API - CPP Dynamic Array with Templates //////////////////////////////////////////////////////////////////////////////// +#define DQN_FOR_ARRAY(indexName, arrayPtr) \ + for (auto indexName = 0; indexName < (arrayPtr)->count; indexName++) + template struct DqnArray { @@ -497,17 +500,21 @@ struct DqnArray void Clear (const bool clearMemory = false); bool Remove (const i64 index); bool RemoveStable(const i64 index); + void RemoveStable(i64 *indexList, const i64 numIndexes); }; template bool DqnArray::Init(const i64 size, const DqnMemAPI api = DqnMemAPI_HeapAllocator()) { - if (this->data) this->Free(); + DQN_ASSERT_HARD(size >= 0); - i64 allocateSize = size * sizeof(T); - DqnMemAPI::Request info = DqnMemAPI::RequestAlloc(api, allocateSize); - this->data = (T *)api.callback(info); - if (!this->data) return false; + if (size > 0) + { + i64 allocateSize = size * sizeof(T); + DqnMemAPI::Request info = DqnMemAPI::RequestAlloc(api, allocateSize, /*zeroClear*/ false); + this->data = (T *)api.callback(info); + if (!this->data) return false; + } this->memAPI = api; this->count = 0; @@ -578,7 +585,7 @@ template T *DqnArray::Push(const T *item, const i64 num) { i64 newSize = this->count + num; - if (newSize > this->max) + if (!this->data || newSize > this->max) { if (!this->Grow()) return nullptr; } @@ -670,6 +677,76 @@ bool DqnArray::RemoveStable(const i64 index) return true; } +template +void DqnArray::RemoveStable(i64 *indexList, const i64 numIndexes) +{ + if (numIndexes == 0 || !indexList) return; + + // NOTE: Sort the index list and ensure we only remove indexes up to the size of our array + Dqn_QuickSort(indexList, numIndexes); + + i64 arrayHighestIndex = this->count - 1; + i64 realCount = numIndexes; + if (indexList[numIndexes - 1] > arrayHighestIndex) + { + i64 realNumIndexes = Dqn_BinarySearch(indexList, numIndexes, arrayHighestIndex, + Dqn_BinarySearchBound_Lower); + // NOTE: If -1, then there's no index in the indexlist that is within the range of our array + // i.e. no index we can remove without out of array bounds access + if (realNumIndexes == -1) + return; + + if (indexList[realNumIndexes] == arrayHighestIndex) + { + realCount = realNumIndexes++; + } + else + { + realCount = realNumIndexes += 2; + } + realCount = DQN_MIN(numIndexes, realCount); + } + + if (realCount == 1) + { + this->RemoveStable(indexList[0]); + } + else + { + i64 indexListIndex = 0; + i64 indexToCopyTo = indexList[indexListIndex++]; + i64 indexToCopyFrom = indexToCopyTo + 1; + i64 deadIndex = indexList[indexListIndex++]; + + bool breakLoop = false; + for (; + indexToCopyFrom < this->count; + indexToCopyTo++, indexToCopyFrom++) + { + while (indexToCopyFrom == deadIndex) + { + deadIndex = indexList[indexListIndex++]; + indexToCopyFrom++; + + breakLoop |= (indexToCopyFrom >= this->count); + breakLoop |= (indexListIndex > realCount); + if (breakLoop) break; + } + if (breakLoop) break; + + this->data[indexToCopyTo] = this->data[indexToCopyFrom]; + } + + for (; indexToCopyFrom < this->count; indexToCopyTo++, indexToCopyFrom++) + { + this->data[indexToCopyTo] = this->data[indexToCopyFrom]; + } + + this->count -= realCount; + DQN_ASSERT(this->count >= 0); + } +} + //////////////////////////////////////////////////////////////////////////////// // #DqnHash Public API //////////////////////////////////////////////////////////////////////////////// @@ -897,7 +974,7 @@ typename DqnHashTable::Entry *DqnHashTable::Make(const char *const key, i3 if (!this->entries[hashIndex]) { i64 index = - Dqn_BinarySearch(this->usedEntries, this->usedEntriesIndex, hashIndex, true); + Dqn_BinarySearch(this->usedEntries, this->usedEntriesIndex, hashIndex, Dqn_BinarySearchBound_Lower); i64 indexToEndAt = index; if (index == -1) indexToEndAt = 0; @@ -951,7 +1028,7 @@ void DqnHashTable::Remove(const char *const key, i32 keyLen) { // Unique entry, so remove this index from the used list as well. i64 indexToRemove = Dqn_BinarySearch(this->usedEntries, this->usedEntriesIndex, - hashIndex, true); + hashIndex, Dqn_BinarySearchBound_Lower); for (i64 i = indexToRemove; i < this->usedEntriesIndex - 1; i++) this->usedEntries[i] = this->usedEntries[i + 1]; @@ -1517,7 +1594,7 @@ template using Dqn_QuickSortLessThanCallback = bool (*)(const T *const , const T *const); template -DQN_FILE_SCOPE void Dqn_QuickSort(T *const array, const i32 size, +DQN_FILE_SCOPE void Dqn_QuickSort(T *const array, const i64 size, Dqn_QuickSortLessThanCallback IsLessThan) { if (!array || size <= 1 || !IsLessThan) return; @@ -1551,10 +1628,10 @@ DQN_FILE_SCOPE void Dqn_QuickSort(T *const array, const i32 size, DqnRndPCG state; state.Init(); - i32 lastIndex = size - 1; - i32 pivotIndex = state.Range(0, lastIndex); - i32 partitionIndex = 0; - i32 startIndex = 0; + auto lastIndex = size - 1; + auto pivotIndex = (i64)state.Range(0, (i32)lastIndex); + auto partitionIndex = 0; + auto startIndex = 0; // Swap pivot with last index, so pivot is always at the end of the array. // This makes logic much simpler. @@ -1569,7 +1646,7 @@ DQN_FILE_SCOPE void Dqn_QuickSort(T *const array, const i32 size, // 4, 5, |7, 8, 2^, 3, 6* // 4, 5, 2, |8, 7, ^3, 6* // 4, 5, 2, 3, |7, 8, ^6* - for (i32 checkIndex = startIndex; checkIndex < lastIndex; checkIndex++) + for (auto checkIndex = startIndex; checkIndex < lastIndex; checkIndex++) { if (IsLessThan(&array[checkIndex], &array[pivotIndex])) { @@ -1589,16 +1666,97 @@ DQN_FILE_SCOPE void Dqn_QuickSort(T *const array, const i32 size, Dqn_QuickSort(array + oneAfterPartitionIndex, (size - oneAfterPartitionIndex), IsLessThan); } +template +DQN_FILE_SCOPE void Dqn_QuickSort(T *const array, const i64 size) +{ + if (!array || size <= 1) return; + +#if 1 + // Insertion Sort, under 24->32 is an optimal amount + const i32 QUICK_SORT_THRESHOLD = 24; + if (size < QUICK_SORT_THRESHOLD) + { + i32 itemToInsertIndex = 1; + while (itemToInsertIndex < size) + { + for (i32 checkIndex = 0; checkIndex < itemToInsertIndex; checkIndex++) + { + if (!(array[checkIndex] < array[itemToInsertIndex])) + { + T itemToInsert = array[itemToInsertIndex]; + for (i32 i = itemToInsertIndex; i > checkIndex; i--) + array[i] = array[i - 1]; + + array[checkIndex] = itemToInsert; + break; + } + } + itemToInsertIndex++; + } + + return; + } +#endif + + DqnRndPCG state; state.Init(); + + auto lastIndex = size - 1; + auto pivotIndex = (i64)state.Range(0, (i32)lastIndex); // TODO(doyle): RNG 64bit + auto partitionIndex = 0; + auto startIndex = 0; + + // Swap pivot with last index, so pivot is always at the end of the array. + // This makes logic much simpler. + DQN_SWAP(T, array[lastIndex], array[pivotIndex]); + pivotIndex = lastIndex; + + // 4^, 8, 7, 5, 2, 3, 6 + if (array[startIndex] < array[pivotIndex]) partitionIndex++; + startIndex++; + + // 4, |8, 7, 5^, 2, 3, 6* + // 4, 5, |7, 8, 2^, 3, 6* + // 4, 5, 2, |8, 7, ^3, 6* + // 4, 5, 2, 3, |7, 8, ^6* + for (auto checkIndex = startIndex; checkIndex < lastIndex; checkIndex++) + { + if (array[checkIndex] < array[pivotIndex]) + { + DQN_SWAP(T, array[partitionIndex], array[checkIndex]); + partitionIndex++; + } + } + + // Move pivot to right of partition + // 4, 5, 2, 3, |6, 8, ^7* + DQN_SWAP(T, array[partitionIndex], array[pivotIndex]); + Dqn_QuickSort(array, partitionIndex); + + // Skip the value at partion index since that is guaranteed to be sorted. + // 4, 5, 2, 3, (x), 8, 7 + i32 oneAfterPartitionIndex = partitionIndex + 1; + Dqn_QuickSort(array + oneAfterPartitionIndex, (size - oneAfterPartitionIndex)); +} + template using Dqn_BinarySearchLessThanCallback = bool (*)(const T&, const T&); -// lowerBound: If true, the return value is the first element lower than the value to find. -// -1 if there is no element lower than the find value (i.e. the 0th element is the find val). -// return: -1 if element not found, otherwise index of the element. +enum Dqn_BinarySearchBound +{ + Dqn_BinarySearchBound_Normal, // Return the index of the first item that matches the find value + Dqn_BinarySearchBound_Lower, // Return the index of the first item lower than the find value + Dqn_BinarySearchBound_Higher, // Return the index of the first item higher than the find value +}; + +// bound: The behaviour of the binary search, +// return: -1 if element not found, otherwise index of the element. +// For higher and lower bounds return -1 if there is no element higher/lower than the +// find value (i.e. -1 if the 0th element is the find val for lower bound). template -DQN_FILE_SCOPE i64 Dqn_BinarySearch(T *const array, const i64 size, const T &find, - const Dqn_BinarySearchLessThanCallback IsLessThan, - const bool lowerBound = false) +DQN_FILE_SCOPE i64 +Dqn_BinarySearch(T *const array, const i64 size, const T &find, + const Dqn_BinarySearchLessThanCallback IsLessThan, + const Dqn_BinarySearchBound bound = Dqn_BinarySearchBound_Normal) { if (size == 0 || !array) return -1; @@ -1608,17 +1766,39 @@ DQN_FILE_SCOPE i64 Dqn_BinarySearch(T *const array, const i64 size, const T &fin while (start <= end) { - if (array[mid] == find) return (lowerBound) ? (mid - 1) : mid; + if (array[mid] == find) + { + if (bound == Dqn_BinarySearchBound_Lower) + { + // NOTE: We can always -1 because at worst case, 0 index will go to -1 which is + // correct behaviour. + return mid - 1; + } + else if (bound == Dqn_BinarySearchBound_Higher) + { + if ((mid + 1) >= size) return -1; + return mid + 1; + } + else + { + return mid; + } + } else if (IsLessThan(array[mid], find)) start = mid + 1; else end = mid - 1; mid = (i64)((start + end) * 0.5f); } - if (lowerBound) + if (bound == Dqn_BinarySearchBound_Lower) { if (find < array[mid]) return -1; return mid; } + else if (bound == Dqn_BinarySearchBound_Higher) + { + if (find > array[mid]) return -1; + return mid; + } else { return -1; @@ -1626,8 +1806,9 @@ DQN_FILE_SCOPE i64 Dqn_BinarySearch(T *const array, const i64 size, const T &fin } template -DQN_FILE_SCOPE i64 Dqn_BinarySearch(T *const array, const i64 size, const i64 &find, - const bool lowerBound = false) +DQN_FILE_SCOPE i64 +Dqn_BinarySearch(T *const array, const i64 size, const i64 &find, + const Dqn_BinarySearchBound bound = Dqn_BinarySearchBound_Normal) { if (size == 0 || !array) return -1; @@ -1637,17 +1818,37 @@ DQN_FILE_SCOPE i64 Dqn_BinarySearch(T *const array, const i64 size, const i64 &f while (start <= end) { - if (array[mid] == find) return (lowerBound) ? (mid - 1) : mid; + if (array[mid] == find) + { + if (bound == Dqn_BinarySearchBound_Lower) + { + return mid - 1; + } + else if (bound == Dqn_BinarySearchBound_Higher) + { + if ((mid + 1) >= size) return -1; + return mid + 1; + } + else + { + return mid; + } + } else if (array[mid] < find) start = mid + 1; else end = mid - 1; mid = (i64)((start + end) * 0.5f); } - if (lowerBound) + if (bound == Dqn_BinarySearchBound_Lower) { if (find < array[mid]) return -1; return mid; } + else if (bound == Dqn_BinarySearchBound_Higher) + { + if (find > array[mid]) return -1; + return mid; + } else { return -1; diff --git a/dqn_unit_test.cpp b/dqn_unit_test.cpp index 72777a4..900693d 100644 --- a/dqn_unit_test.cpp +++ b/dqn_unit_test.cpp @@ -1069,185 +1069,326 @@ void DqnRect_Test() void DqnArray_TestInternal(const DqnMemAPI memAPI) { - DqnArray array = {}; if (1) { - DQN_ASSERT(array.Init(1, memAPI)); - DQN_ASSERT(array.max >= 1); - DQN_ASSERT(array.count == 0); - - // Test basic insert + DqnArray array = {}; if (1) { - DqnV2 va = DqnV2(5, 10); - DQN_ASSERT(array.Push(va)); - - DqnV2 vb = array.data[0]; - DQN_ASSERT(DqnV2_Equals(va, vb)); - + DQN_ASSERT(array.Init(1, memAPI)); DQN_ASSERT(array.max >= 1); - DQN_ASSERT(array.count == 1); - LogSuccess("DqnArray(): Test basic insert"); - } + DQN_ASSERT(array.count == 0); + + // Test basic insert + if (1) + { + DqnV2 va = DqnV2(5, 10); + DQN_ASSERT(array.Push(va)); + + DqnV2 vb = array.data[0]; + DQN_ASSERT(DqnV2_Equals(va, vb)); + + DQN_ASSERT(array.max >= 1); + DQN_ASSERT(array.count == 1); + LogSuccess("DqnArray(): Test basic insert"); + } + + // Test array resizing and freeing + if (1) + { + DqnV2 va = DqnV2(10, 15); + DQN_ASSERT(array.Push(va)); + + DqnV2 vb = array.data[0]; + DQN_ASSERT(DqnV2_Equals(va, vb) == false); + + vb = array.data[1]; + DQN_ASSERT(DqnV2_Equals(va, vb) == true); + + DQN_ASSERT(array.max >= 2); + DQN_ASSERT(array.count == 2); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 3); + DQN_ASSERT(array.count == 3); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 4); + DQN_ASSERT(array.count == 4); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 5); + DQN_ASSERT(array.count == 5); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 6); + DQN_ASSERT(array.count == 6); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 7); + DQN_ASSERT(array.count == 7); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 8); + DQN_ASSERT(array.count == 8); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 9); + DQN_ASSERT(array.count == 9); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 10); + DQN_ASSERT(array.count == 10); + + DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.max >= 11); + DQN_ASSERT(array.count == 11); + + DqnV2 vc = DqnV2(90, 100); + DQN_ASSERT(array.Push(vc)); + DQN_ASSERT(array.max >= 12); + DQN_ASSERT(array.count == 12); + DQN_ASSERT(DqnV2_Equals(vc, array.data[11])); + + LogSuccess("DqnArray(): Test resizing and free"); + } + } + DQN_ASSERT(array.Free()); - // Test array resizing and freeing if (1) { - DqnV2 va = DqnV2(10, 15); - DQN_ASSERT(array.Push(va)); + DQN_ASSERT(array.Init(1, memAPI)); + DQN_ASSERT(array.max >= 1); + DQN_ASSERT(array.count == 0); + LogSuccess("DqnArray(): Empty array"); + } + DQN_ASSERT(array.Free()); - DqnV2 vb = array.data[0]; - DQN_ASSERT(DqnV2_Equals(va, vb) == false); + if (1) + { + DqnV2 a = DqnV2(1, 2); + DqnV2 b = DqnV2(3, 4); + DqnV2 c = DqnV2(5, 6); + DqnV2 d = DqnV2(7, 8); - vb = array.data[1]; - DQN_ASSERT(DqnV2_Equals(va, vb) == true); + DQN_ASSERT(array.Init(16, memAPI)); + DQN_ASSERT(array.Remove(0) == false); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 0); - DQN_ASSERT(array.max >= 2); - DQN_ASSERT(array.count == 2); + array.Clear(); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 0); - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 3); - DQN_ASSERT(array.count == 3); - - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 4); + DQN_ASSERT(array.Push(a)); + DQN_ASSERT(array.Push(b)); + DQN_ASSERT(array.Push(c)); + DQN_ASSERT(array.Push(d)); + DQN_ASSERT(array.max >= 16); DQN_ASSERT(array.count == 4); - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 5); - DQN_ASSERT(array.count == 5); + DQN_ASSERT(array.Remove(0)); + DQN_ASSERT(DqnV2_Equals(array.data[0], d)); + DQN_ASSERT(DqnV2_Equals(array.data[1], b)); + DQN_ASSERT(DqnV2_Equals(array.data[2], c)); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 3); - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 6); - DQN_ASSERT(array.count == 6); + DQN_ASSERT(array.Remove(2)); + DQN_ASSERT(DqnV2_Equals(array.data[0], d)); + DQN_ASSERT(DqnV2_Equals(array.data[1], b)); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 2); - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 7); - DQN_ASSERT(array.count == 7); + DQN_ASSERT(array.Remove(100) == false); + DQN_ASSERT(DqnV2_Equals(array.data[0], d)); + DQN_ASSERT(DqnV2_Equals(array.data[1], b)); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 2); - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 8); - DQN_ASSERT(array.count == 8); - - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 9); - DQN_ASSERT(array.count == 9); - - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 10); - DQN_ASSERT(array.count == 10); - - DQN_ASSERT(array.Push(va)); - DQN_ASSERT(array.max >= 11); - DQN_ASSERT(array.count == 11); - - DqnV2 vc = DqnV2(90, 100); - DQN_ASSERT(array.Push(vc)); - DQN_ASSERT(array.max >= 12); - DQN_ASSERT(array.count == 12); - DQN_ASSERT(DqnV2_Equals(vc, array.data[11])); - - LogSuccess("DqnArray(): Test resizing and free"); + array.Clear(); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 0); + LogSuccess("DqnArray(): Test removal"); } + DQN_ASSERT(array.Free()); + + if (1) + { + DqnV2 a = DqnV2(1, 2); + DqnV2 b = DqnV2(3, 4); + DqnV2 c = DqnV2(5, 6); + DqnV2 d = DqnV2(7, 8); + + DQN_ASSERT(array.Init(16, memAPI)); + + DQN_ASSERT(array.Push(a)); + DQN_ASSERT(array.Push(b)); + DQN_ASSERT(array.Push(c)); + DQN_ASSERT(array.Push(d)); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 4); + + array.RemoveStable(0); + DQN_ASSERT(DqnV2_Equals(array.data[0], b)); + DQN_ASSERT(DqnV2_Equals(array.data[1], c)); + DQN_ASSERT(DqnV2_Equals(array.data[2], d)); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 3); + + array.RemoveStable(1); + DQN_ASSERT(DqnV2_Equals(array.data[0], b)); + DQN_ASSERT(DqnV2_Equals(array.data[1], d)); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 2); + + array.RemoveStable(1); + DQN_ASSERT(DqnV2_Equals(array.data[0], b)); + DQN_ASSERT(array.max >= 16); + DQN_ASSERT(array.count == 1); + + LogSuccess("DqnArray(): Test stable removal"); + } + DQN_ASSERT(array.Free()); } - DQN_ASSERT(array.Free()); if (1) { - DQN_ASSERT(array.Init(1, memAPI)); - DQN_ASSERT(array.max >= 1); - DQN_ASSERT(array.count == 0); - LogSuccess("DqnArray(): Empty array"); + // Test normal remove list scenario + if (1) + { + i64 indexesToFree[] = {3, 2, 1, 0}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + DQN_ASSERT(array.count == 0); + array.Free(); + } + + // Test all indexes invalid + if (1) + { + i64 indexesToFree[] = {100, 200, 300, 400}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + DQN_ASSERT(array.count == 4); + DQN_ASSERT(array.data[0] == 128); + DQN_ASSERT(array.data[1] == 32); + DQN_ASSERT(array.data[2] == 29); + DQN_ASSERT(array.data[3] == 31); + array.Free(); + } + + // Test remove singular index + if (1) + { + i64 indexesToFree[] = {1}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + DQN_ASSERT(array.count == 3); + DQN_ASSERT(array.data[0] == 128); + DQN_ASSERT(array.data[1] == 29); + DQN_ASSERT(array.data[2] == 31); + array.Free(); + } + + // Test remove singular invalid index + if (1) + { + i64 indexesToFree[] = {100}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + DQN_ASSERT(array.count == 4); + DQN_ASSERT(array.data[0] == 128); + DQN_ASSERT(array.data[1] == 32); + DQN_ASSERT(array.data[2] == 29); + DQN_ASSERT(array.data[3] == 31); + array.Free(); + } + + // Test remove second last index + if (1) + { + i64 indexesToFree[] = {2}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + DQN_ASSERT(array.count == 3); + DQN_ASSERT(array.data[0] == 128); + DQN_ASSERT(array.data[1] == 32); + DQN_ASSERT(array.data[2] == 31); + array.Free(); + } + + // Test remove last 2 indexes + if (1) + { + i64 indexesToFree[] = {2, 3}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + DQN_ASSERT(array.count == 2); + DQN_ASSERT(array.data[0] == 128); + DQN_ASSERT(array.data[1] == 32); + array.Free(); + } + + // Test invalid free index doesn't delete out of bounds + if (1) + { + i64 indexesToFree[] = {30, 1, 3}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + + DQN_ASSERT(array.count == 2); + DQN_ASSERT(array.data[0] == 128); + DQN_ASSERT(array.data[1] == 29); + array.Free(); + } + + // Test a free list including the first index + if (1) + { + i64 indexesToFree[] = {0, 1, 2}; + i32 intList[] = {128, 32, 29, 31}; + + DqnArray array; + array.Init(DQN_ARRAY_COUNT(intList), memAPI); + array.Push(intList, DQN_ARRAY_COUNT(intList)); + array.RemoveStable(indexesToFree, DQN_ARRAY_COUNT(indexesToFree)); + + DQN_ASSERT(array.count == 1); + DQN_ASSERT(array.data[0] == 31); + array.Free(); + } + + + LogSuccess("DqnArray(): Test stable removal with list of indexes"); } - DQN_ASSERT(array.Free()); - - if (1) - { - DqnV2 a = DqnV2(1, 2); - DqnV2 b = DqnV2(3, 4); - DqnV2 c = DqnV2(5, 6); - DqnV2 d = DqnV2(7, 8); - - DQN_ASSERT(array.Init(16, memAPI)); - DQN_ASSERT(array.Remove(0) == false); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 0); - - array.Clear(); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 0); - - DQN_ASSERT(array.Push(a)); - DQN_ASSERT(array.Push(b)); - DQN_ASSERT(array.Push(c)); - DQN_ASSERT(array.Push(d)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 4); - - DQN_ASSERT(array.Remove(0)); - DQN_ASSERT(DqnV2_Equals(array.data[0], d)); - DQN_ASSERT(DqnV2_Equals(array.data[1], b)); - DQN_ASSERT(DqnV2_Equals(array.data[2], c)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 3); - - DQN_ASSERT(array.Remove(2)); - DQN_ASSERT(DqnV2_Equals(array.data[0], d)); - DQN_ASSERT(DqnV2_Equals(array.data[1], b)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 2); - - DQN_ASSERT(array.Remove(100) == false); - DQN_ASSERT(DqnV2_Equals(array.data[0], d)); - DQN_ASSERT(DqnV2_Equals(array.data[1], b)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 2); - - array.Clear(); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 0); - LogSuccess("DqnArray(): Test removal"); - } - DQN_ASSERT(array.Free()); - - if (1) - { - DqnV2 a = DqnV2(1, 2); - DqnV2 b = DqnV2(3, 4); - DqnV2 c = DqnV2(5, 6); - DqnV2 d = DqnV2(7, 8); - - DQN_ASSERT(array.Init(16, memAPI)); - - DQN_ASSERT(array.Push(a)); - DQN_ASSERT(array.Push(b)); - DQN_ASSERT(array.Push(c)); - DQN_ASSERT(array.Push(d)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 4); - - array.RemoveStable(0); - DQN_ASSERT(DqnV2_Equals(array.data[0], b)); - DQN_ASSERT(DqnV2_Equals(array.data[1], c)); - DQN_ASSERT(DqnV2_Equals(array.data[2], d)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 3); - - array.RemoveStable(1); - DQN_ASSERT(DqnV2_Equals(array.data[0], b)); - DQN_ASSERT(DqnV2_Equals(array.data[1], d)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 2); - - array.RemoveStable(1); - DQN_ASSERT(DqnV2_Equals(array.data[0], b)); - DQN_ASSERT(array.max >= 16); - DQN_ASSERT(array.count == 1); - - LogSuccess("DqnArray(): Test stable removal"); - } - - DQN_ASSERT(array.Free()); } void DqnArray_TestRealDataInternal(DqnArray *array) @@ -1999,7 +2140,7 @@ void DqnQuickSort_Test() u32 *stdArray = (u32 *)stack.Push(sizeInBytes); DQN_ASSERT(dqnCPPArray && stdArray); - f64 dqnCPPTimings[2] = {}; + f64 dqnCPPTimings[5] = {}; f64 stdTimings[DQN_ARRAY_COUNT(dqnCPPTimings)] = {}; f64 dqnCPPAverage = 0; @@ -2017,12 +2158,9 @@ void DqnQuickSort_Test() // Time Dqn_QuickSort { f64 start = DqnTimer_NowInS(); - Dqn_QuickSort( - dqnCPPArray, numInts, - [](const u32 *const a, const u32 *const b) -> bool { return *a < *b; }); + Dqn_QuickSort(dqnCPPArray, numInts); f64 duration = DqnTimer_NowInS() - start; - dqnCPPTimings[timingsIndex] = duration; dqnCPPAverage += duration; printf("Dqn_QuickSort: %f vs ", dqnCPPTimings[timingsIndex]); @@ -2187,24 +2325,22 @@ void Dqn_BinarySearch_Test() if (1) { i32 array[] = {1, 2, 3}; - - const bool LOWER_BOUND = true; - i64 result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 0, LOWER_BOUND); + i64 result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == -1); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 1, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == -1); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 2, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 0); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 3, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 1); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 4, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 2); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 5, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 2); LogSuccess("Dqn_BinarySearch(): Lower bound with odd sized array"); } @@ -2213,30 +2349,78 @@ void Dqn_BinarySearch_Test() { i32 array[] = {1, 2, 3, 4}; - const bool LOWER_BOUND = true; - i64 result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 0, LOWER_BOUND); + i64 result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == -1); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 1, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == -1); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 2, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 0); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 3, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 1); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 4, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 2); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 5, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 3); - result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 6, LOWER_BOUND); + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 6, Dqn_BinarySearchBound_Lower); DQN_ASSERT(result == 3); - LogSuccess("Dqn_BinarySearch(): Lower bound with even sized array"); } + + if (1) + { + i32 array[] = {1, 2, 3}; + i64 result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == 0); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == 1); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == 2); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == -1); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == -1); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == -1); + LogSuccess("Dqn_BinarySearch(): Higher bound with odd sized array"); + } + + if (1) + { + i32 array[] = {1, 2, 3, 4}; + + i64 result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 0, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == 0); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 1, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == 1); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 2, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == 2); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 3, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == 3); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 4, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == -1); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 5, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == -1); + + result = Dqn_BinarySearch(array, DQN_ARRAY_COUNT(array), 6, Dqn_BinarySearchBound_Higher); + DQN_ASSERT(result == -1); + LogSuccess("Dqn_BinarySearch(): Higher bound with even sized array"); + } } int main(void)