Добавить медианный метод в список
Я хотел бы переопределить объект 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>
в следующем ответе:
Вычислить медиану в С#
Ответ 5
Вы можете создать метод расширения для типа коллекции, который вы хотите поддерживать. Тогда вы сможете называть его, как если бы его часть этого класса.
MSDN - документация и примеры методов расширения
Ответ 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);
}
}