/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Utility
{
using System.Collections.Generic;
///
/// The QuickSelect algorithm is a selection algorithm that uses a similar method as the QuickSort sorting algorithm. More information can be found on this page:
/// https://en.wikipedia.org/wiki/Quickselect.
///
public class QuickSelect
{
///
/// Returns the element that is the Kth smallest within the array.
///
/// The array that should be searched.
/// The number of elements to search within the array.
/// The nth smallest value to retrieve. 0 indicates the smallest element, endIndex - 1 indicates the largest.
/// The IComparer used to compare the array.
/// The element that is the Kth smallest within the array.
public static T SmallestK(T[] array, int arrayCount, int k, IComparer comparer)
{
if (k > arrayCount - 1) {
k = arrayCount - 1;
}
return SmallestK(array, 0, arrayCount - 1, k, comparer);
}
///
/// Returns the element that is the Kth smallest within the array.
///
/// The array that should be searched.
/// The starting index of the array.
/// The ending index of the array.
/// The nth smallest value to retrieve. 0 indicates the smallest element, endIndex - 1 indicates the largest.
/// The IComparer used to compare the array.
/// The element that is the Kth smallest within the array.
private static T SmallestK(T[] array, int startIndex, int endIndex, int k, IComparer comparer)
{
if (startIndex == endIndex) {
return array[startIndex];
}
// Similar to the QuickSort algorithm, split the array into a subset and reorder based on the pivot.
var pivotIndex = Partition(array, startIndex, endIndex, comparer);
// If the pivot is same as k then the kth smallest value has been found.
if (pivotIndex == k) {
return array[pivotIndex];
}
// If the pivot is less, then the Kth smallest element is in the right subgroup.
if (pivotIndex < k) {
return SmallestK(array, pivotIndex + 1, endIndex, k, comparer);
}
// If the pivot is greater, then the Kth smallest element is in the left subgroup.
return SmallestK(array, startIndex, pivotIndex - 1, k, comparer);
}
///
/// Partition the array into two groups based on the pivot. All values smaller than the pivot will be moved to the left, and all values greater will be moved
/// to the right. This is similar to the QuickSort algorithm.
///
/// The array that should be sorted.
/// The starting index of the array.
/// The ending index of the array.
/// The nth smallest value to retrieve. 0 indicates the smallest element, endIndex - 1 indicates the largest.
/// The IComparer used to compare the array.
/// The position of the pivot.
private static int Partition(T[] array, int startIndex, int endIndex, IComparer comparer)
{
var pivotIndex = UnityEngine.Random.Range(startIndex, endIndex + 1);
// The pivot has not been reordered yet. Move all elements that are less than the pivot to the left, and move all elements that are greater to the right.
var pivotValue = array[pivotIndex];
// The pivot should be moved to the end so it won't be compared against itself.
Swap(array, pivotIndex, endIndex);
var index = startIndex;
for (int i = startIndex; i < endIndex; ++i) {
if (comparer.Compare(array[i], pivotValue) < 0) {
Swap(array, index, i);
index++;
}
}
// Ensure the pivot is on the right of the smaller values.
Swap(array, index, endIndex);
return index;
}
///
/// Swap the first and second elements.
///
/// The array that should be sorted.
/// The first index that should be swapped.
/// The second index that should be swapped.
private static void Swap(T[] array, int firstIndex, int secondIndex)
{
var temp = array[firstIndex];
array[firstIndex] = array[secondIndex];
array[secondIndex] = temp;
}
}
}