Add a Median Method to a List

你说的曾经没有我的故事 提交于 2019-11-29 17:31:18

问题


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 function:

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?


回答1:


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];
    }
}



回答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.




回答3:


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.




回答4:


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#




回答5:


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




回答6:


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.




回答7:


I'll make some corrections to your method:

replace this:

     int[] tempArray = array; 

with:

     int[] tempArray = (int[])array.Clone();



回答8:


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


来源:https://stackoverflow.com/questions/5275115/add-a-median-method-to-a-list

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!