14

我想在 C# 中覆盖 List 对象,以便添加像 Sum 或 Average 之类的中值方法。我已经找到了这个功能:

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

你能告诉我该怎么做吗?

4

8 回答 8

24

使用扩展方法,并复制输入的数组/列表。

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];
    }
}
于 2011-03-11T16:05:45.117 回答
16

不要使用该功能。它存在严重缺陷。看一下这个:

int[] tempArray = array;     
Array.Sort(tempArray); 

数组是C# 中的引用类型这会对您提供的数组进行排序,而不是副本。 获取数组的中位数不应该改变它的顺序;它可能已经按不同的顺序排序。

用于Array.Copy首先制作数组的副本,然后对副本进行排序。

于 2011-03-11T15:59:08.383 回答
6

我肯定会做那些扩展方法

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

然后,您可以通过以下方式使用这些方法:

List<int> values = new List<int>{ 1, 2, 3, 4, 5 };
var median = values.Median();

更新

哦...正如 Eric 提到的,您应该找到 Median 的另一个实现。您提供的那个不仅修改了原始数组,而且如果我没看错的话,它还会返回一个整数而不是预期的小数。

于 2011-03-11T15:53:43.247 回答
2

您可能不想使用排序来查找中位数,因为否则有更有效的方法来计算它。IList<T>您可以在我的以下答案中找到此代码,该代码还添加了 Median 作为扩展方法:

在c#中计算中位数

于 2014-03-28T01:15:31.413 回答
0

您可以为要支持的集合类型创建扩展方法。然后你就可以调用它,就像它是那个类的一部分一样。

MSDN - 扩展方法文档和示例

于 2011-03-11T15:52:41.720 回答
0

Average 和 sum 是可用于任何 IEnumerable 的扩展方法,提供正确的转换函数作为参数MSDN

decimal Median<TSource>(this IEnumerable<TSource> collection, Func<TSource,decimal> transform)
{
   var array = collection.Select(x=>transform(x)).ToArray();
   [...]
   return median;
}

transform 将获取一个集合项并将其转换为小数(可平均且可比较)

我不会在这里详细介绍 Median 方法的实现,但这并不复杂。

编辑:我看到您添加了输出十进制平均值的进一步要求。

PS:为简洁起见,省略了参数检查。

于 2011-03-11T16:05:25.507 回答
0

我将对您的方法进行一些更正:

替换这个:

     int[] tempArray = array; 

和:

     int[] tempArray = (int[])array.Clone();
于 2014-01-17T03:59:32.650 回答
0

我创建了自己的解决方案我在 SQL 服务器中有大表,并且 .ToList() 和 .ToArray() 不能正常工作(你在做任何其他事情之前从数据库中提取所有行,我需要的只是记录的长度,中间 1 或 2 行(奇数或偶数)如果有人感兴趣我有一个带有 Expression 的版本返回 TResult 而不是十进制

   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);
    }
}
于 2016-07-26T04:12:52.983 回答