开发者

Add a Median Method to a List

开发者 https://www.devze.com 2023-02-16 19:06 出处:网络
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:

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);
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消