// ---------------------------------------------------------------------------------------------- // // Program.cs - QuickSelect // // ---------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; namespace QuickSelect { internal static class Program { #region Static Members private static void Main() { var inputArray = new[] {9, 8, 7, 6, 5, 0, 1, 2, 3, 4}; // Loop 10 times Console.WriteLine( "Loop quick select 10 times." ); for( var i = 0 ; i < 10 ; i++ ) { Console.Write( inputArray.NthSmallestElement( i ) ); if( i < 9 ) Console.Write( ", " ); } Console.WriteLine(); // And here is then more effective way to get N smallest elements from vector in order by using quick select algorithm // Basically we are here just sorting array (taking 10 smallest from array which length is 10) Console.WriteLine( "Just sort 10 elements." ); Console.WriteLine( string.Join( ", ", inputArray.TakeSmallest( 10 ).OrderBy( v => v ).Select( v => v.ToString() ).ToArray() ) ); // Here we are actually doing quick select once by taking only 4 smallest from array. Console.WriteLine( "Get 4 smallest and sort them." ); Console.WriteLine( string.Join( ", ", inputArray.TakeSmallest( 4 ).OrderBy( v => v ).Select( v => v.ToString() ).ToArray() ) ); Console.WriteLine( "< Press any key >" ); Console.ReadKey(); } #endregion } internal static class ArrayExtension { #region Static Members /// /// Return specified number of smallest elements from array. /// /// The type of the elements of array. Type must implement IComparable(T) interface. /// The array to return elemnts from. /// The number of smallest elements to return. /// An IEnumerable(T) that contains the specified number of smallest elements of the input array. Returned elements are NOT sorted. public static IEnumerable TakeSmallest( this T[] array, int count ) where T : IComparable { if( count < 0 ) throw new ArgumentOutOfRangeException( "count", "Count is smaller than 0." ); if( count == 0 ) return new T[0]; if( array.Length <= count ) return array; return QuickSelectSmallest( array, count - 1 ).Take( count ); } /// /// Returns N:th smallest element from the array. /// /// The type of the elements of array. Type must implement IComparable(T) interface. /// The array to return elemnt from. /// Nth element. 0 is smallest element, when array.Length - 1 is largest element. /// N:th smalles element from the array. public static T NthSmallestElement( this T[] array, int n ) where T : IComparable { if( n < 0 || n > array.Length - 1 ) throw new ArgumentOutOfRangeException( "n", n, string.Format( "n should be between 0 and {0} it was {1}.", array.Length - 1, n ) ); if( array.Length == 0 ) throw new ArgumentException( "Array is empty.", "array" ); if( array.Length == 1 ) return array[ 0 ]; return QuickSelectSmallest( array, n )[ n ]; } /// /// Partially sort array such way that elements before index position n are smaller or equal than elemnt at position n. And elements after n are larger or equal. /// /// The type of the elements of array. Type must implement IComparable(T) interface. /// The array which elements are being partially sorted. This array is not modified. /// Nth smallest element. /// Partially sorted array. private static T[] QuickSelectSmallest( T[] input, int n ) where T : IComparable { // Let's not mess up with our input array // For very large arrays - we should optimize this somehow - or just mess up with our input var partiallySortedArray = (T[]) input.Clone(); // Initially we are going to execute quick select to entire array var startIndex = 0; var endIndex = input.Length - 1; // Selecting initial pivot // Maybe we are lucky and array is sorted initially? var pivotIndex = n; // Loop until there is nothing to loop (this actually shouldn't happen - we should find our value before we run out of values) var r = new Random(); while( endIndex > startIndex ) { pivotIndex = QuickSelectPartition( partiallySortedArray, startIndex, endIndex, pivotIndex ); if( pivotIndex == n ) // We found our n:th smallest value - it is stored to pivot index break; if( pivotIndex > n ) // Array before our pivot index have more elements that we are looking for endIndex = pivotIndex - 1; else // Array before our pivot index has less elements that we are looking for startIndex = pivotIndex + 1; // Omnipotent beings don't need to roll dices - but we do... // Randomly select a new pivot index between end and start indexes (there are other methods, this is just most brutal and simplest) pivotIndex = r.Next( startIndex, endIndex ); } return partiallySortedArray; } /// /// Sort elements in sub array between startIndex and endIndex, such way that elements smaller than or equal with value initially stored to pivot index are before /// new returned pivot value index. /// /// The type of the elements of array. Type must implement IComparable(T) interface. /// The array that is being sorted. /// Start index of sub array. /// End index of sub array. /// Pivot index. /// New pivot index. Value that was initially stored to is stored to this newly returned index. All elements before this index are /// either smaller or equal with pivot value. All elements after this index are larger than pivot value. /// This method modifies paremater array. private static int QuickSelectPartition( this T[] array, int startIndex, int endIndex, int pivotIndex ) where T : IComparable { var pivotValue = array[ pivotIndex ]; // Initially we just assume that value in pivot index is largest - so we move it to end (makes also for loop more straight forward) array.Swap( pivotIndex, endIndex ); for( var i = startIndex ; i < endIndex ; i++ ) { if( array[ i ].CompareTo( pivotValue ) > 0 ) continue; // Value stored to i was smaller than or equal with pivot value - let's move it to start array.Swap( i, startIndex ); // Move start one index forward startIndex++; } // Start index is now pointing to index where we should store our pivot value from end of array array.Swap( endIndex, startIndex ); return startIndex; } private static void Swap( this T[] array, int index1, int index2 ) { if( index1 == index2 ) return; var temp = array[ index1 ]; array[ index1 ] = array[ index2 ]; array[ index2 ] = temp; } #endregion } }