I would like to override the List object in C# in order to add a Median method like Sum or Average. I already found this fun开发者_如何学Cction:
public static decimal GetMedian(int[] array)
{
int[] tempArray = array;
int count = tempArray.Length;
Array.Sort(tempArray);
decimal medianValue = 0;
if (count % 2 == 0)
{
// count is even, need to get the middle two elements, add them together, then divide by 2
int middleElement1 = tempArray[(count / 2) - 1];
int middleElement2 = tempArray[(count / 2)];
medianValue = (middleElement1 + middleElement2) / 2;
}
else
{
// count is odd, simply get the middle element.
medianValue = tempArray[(count / 2)];
}
return medianValue;
}
Can you tell me how to do that?
Use an extension method, and make a copy of the inputted array/list.
public static decimal GetMedian(this IEnumerable<int> source)
{
// Create a copy of the input, and sort the copy
int[] temp = source.ToArray();
Array.Sort(temp);
int count = temp.Length;
if (count == 0)
{
throw new InvalidOperationException("Empty collection");
}
else if (count % 2 == 0)
{
// count is even, average two middle elements
int a = temp[count / 2 - 1];
int b = temp[count / 2];
return (a + b) / 2m;
}
else
{
// count is odd, return the middle element
return temp[count / 2];
}
}
Do not use that function. It is deeply flawed. Check this out:
int[] tempArray = array;
Array.Sort(tempArray);
Arrays are reference types in C#. This sorts the array that you give it, not a copy. Obtaining the median of an array should not change its order; it might already be sorted into a different order.
Use Array.Copy
to first make a copy of the array and then sort the copy.
I would definitely make those Extension Methods:
public static class EnumerableExtensions
{
public static decimal Median(this IEnumerable<int> list)
{
// Implementation goes here.
}
public static int Sum(this IEnumerable<int> list)
{
// While you could implement this, you could also use Enumerable.Sum()
}
}
You could then use those methods in the following way:
List<int> values = new List<int>{ 1, 2, 3, 4, 5 };
var median = values.Median();
Update
Oh...and as Eric mentions, you should find another implementation of Median. The one you provided not only modifies the original array in place but, if I'm reading it correctly, will also return an integer rather than the expected decimal.
You probably don't want to use sort to find median because there are more efficient way to calculate it otherwise. You can find the code for this which also adds Median as extension method for IList<T>
in my following answer:
Calculate median in c#
You could create an extension method for the collection type you want to support. Then you'll be able to call it, just as if its part of that class.
MSDN - Extension Methods documentation and examples
Average and sum are Extension methods available for any IEnumerable providing the correct transformation function as a parameter MSDN
decimal Median<TSource>(this IEnumerable<TSource> collection, Func<TSource,decimal> transform)
{
var array = collection.Select(x=>transform(x)).ToArray();
[...]
return median;
}
transform will take a collection item and transform it to a decimal (averagable and comparable)
I won't dive here in the detail of the Median metho implementation, but it's not really complicated.
Edit: I Saw you added the further requirement of outputing a decimal average.
PS: parameter checking is ometted for brievety.
I'll make some corrections to your method:
replace this:
int[] tempArray = array;
with:
int[] tempArray = (int[])array.Clone();
I created my own solution I have big tables in SQL server and .ToList() and .ToArray() doesn't work well (you pull all rows from database bofore doing anything else, what I need simply is the length of the records, and middle 1 or 2 rows (odd or even) if anyone interested I have a version with Expression returns TResult instead if decimal
public static decimal MedianBy<T, TResult>(this IQueryable<T> sequence, Expression<Func<T, TResult>> getValue)
{
var count = sequence.Count();
//Use Expression bodied fuction otherwise it won't be translated to SQL query
var list = sequence.OrderByDescending(getValue).Select(getValue);
var mid = count / 2;
if (mid == 0)
{
throw new InvalidOperationException("Empty collection");
}
if (count % 2 == 0)
{
var elem1 = list.Skip(mid - 1).FirstOrDefault();
var elem2 = list.Skip(mid).FirstOrDefault();
return (Convert.ToDecimal(elem1) + Convert.ToDecimal(elem2)) / 2M;
//TODO: search for a way to devide 2 for different types (int, double, decimal, float etc) till then convert to decimal to include all posibilites
}
else
{
return Convert.ToDecimal(list.Skip(mid).FirstOrDefault());
//ElementAt Doesn't work with SQL
//return list.ElementAt(mid);
}
}
精彩评论