using System; using System.Collections.Generic; using System.Linq; namespace AdditionalMathf { public static class NOrderStatExtension { /// /// Partitions the given list around a pivot element such that all elements on left of pivot are <= pivot /// and the ones at thr right are > pivot. This method can be used for sorting, N-order statistics such as /// as median finding algorithms. /// Pivot is selected ranodmly if random number generator is supplied else its selected as last element in the list. /// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 171 /// private static int Partition(this IList list, int start, int end, Random rnd = null) where T : IComparable { if (rnd != null) list.Swap(end, rnd.Next(start, end + 1)); var pivot = list[end]; var lastLow = start - 1; for (var i = start; i < end; i++) if (list[i].CompareTo(pivot) <= 0) list.Swap(i, ++lastLow); list.Swap(end, ++lastLow); return lastLow; } /// /// Returns Nth smallest element from the list. Here n starts from 0 so that n=0 returns minimum, n=1 returns 2nd /// smallest element etc. /// Note: specified list would be mutated in the process. /// Reference: Introduction to Algorithms 3rd Edition, Corman et al, pp 216 /// public static T NthOrderStatistic(this IList list, int n, Random rnd = null) where T : IComparable { return NthOrderStatistic(list, n, 0, list.Count - 1, rnd); } private static T NthOrderStatistic(this IList list, int n, int start, int end, Random rnd) where T : IComparable { while (true) { var pivotIndex = list.Partition(start, end, rnd); if (pivotIndex == n) return list[pivotIndex]; if (n < pivotIndex) end = pivotIndex - 1; else start = pivotIndex + 1; } } public static void Swap(this IList list, int i, int j) { if (i == j) //This check is not required but Partition function may make many calls so its for perf reason return; var temp = list[i]; list[i] = list[j]; list[j] = temp; } /// /// Note: specified list would be mutated in the process. /// public static T Median(this IList list) where T : IComparable { return list.NthOrderStatistic((list.Count - 1) / 2); } public static double Median(this IEnumerable sequence, Func getValue) { var list = sequence.Select(getValue).ToList(); var mid = (list.Count - 1) / 2; return list.NthOrderStatistic(mid); } } }