Добавить медианный метод в список

Я хотел бы переопределить объект 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;
}

Можете ли вы рассказать мне, как это сделать?

Ответ 1

Используйте метод расширения и создайте копию введенного массива/списка.

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

Не используйте эту функцию. Это глубоко ошибочно. Проверьте это:

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

Массивы являются ссылочными типами в С#. Сортирует массив, который вы ему даете, а не копию. Получение медианы массива не должно изменять его порядок; он уже может быть отсортирован в другом порядке.

Используйте Array.Copy, чтобы сначала сделать копию массива, а затем отсортировать копию.

Ответ 3

Я бы определенно сделал те методы расширения:

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

Обновление

О... и, как упоминает Эрик, вы должны найти другую реализацию медианной. Тот, который вы предоставили, не только изменяет исходный массив на месте, но, если я его правильно читаю, также будет возвращать целое число, а не ожидаемое десятичное число.

Ответ 4

Вероятно, вы не хотите использовать сортировку для поиска медианы, потому что есть более эффективный способ ее вычисления. Вы можете найти код для этого, который также добавляет Median как метод расширения для IList<T> в следующем ответе:

Вычислить медиану в С#

Ответ 6

Среднее значение и сумма - это методы расширения, доступные для любого IEnumerable, обеспечивающего правильную функцию преобразования как параметр MSDN

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

преобразует элемент коллекции и преобразует его в десятичную (среднюю и сопоставимую)

Я не буду погружаться здесь подробно в медианную методологию, но это не очень сложно.

Изменить: я увидел, что вы добавили дополнительное требование вывода десятичного среднего значения.

PS: проверка параметров выполняется для brievety.

Ответ 7

Я внесу некоторые исправления в ваш метод:

замените это:

     int[] tempArray = array; 

с:

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

Ответ 8

Я создал собственное решение. У меня большие таблицы на SQL-сервере, а .ToList() и .ToArray() не работают хорошо (вы вытаскиваете все строки из базы данных, делая что-то еще, то, что мне нужно, это просто длина записи и средние 1 или 2 строки (нечетные или четные) если кто-то заинтересован, у меня есть версия с выражением, возвращает 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);
    }
}