2011-06-09 3 views
1

Это, вероятно, очень просто, но мои попытки (руководствуясь Intellisense и MSDN) все были в стороне.Как написать функцию расширения, чтобы вернуть среднее значение нестандартного типа?

Если у меня есть класс, который содержит 3 двойных, как я могу получить среднее из их списка?

class DataPoint 
{ 
    public int time; 
    public int X; 
    public int Y; 
    public int Z; 
    // Constructor omitted 
} 

class Main 
{ 
    List<DataPoint> points = new List<DataPoint>(); 
    // Populate list 
    DataPoint averagePoint = points.Average(someMagicHere); 
} 

Я хочу averagePoint содержать time, x, y & z значения, среднее значение этих свойств элементов, составляющих список. Как мне это сделать? Бит, с которым я борюсь, - это (я думаю) someMagicHere, но с самого начала я мог использовать совершенно неправильный подход.

+0

Ваши имена переменных никогда не должны начинаться с верхнего регистра. Этот стиль зарезервирован для типов и методов. то есть 'points', а не' Points'. –

+0

Ах да, извините, был неаккуратно в письменном виде. Будет исправлено. –

ответ

4
static class DataPointExtensions 
{ 
public static DataPoint Average (this IEnumerable<DataPoint> points) 
{ 
    int sumX=0, sumY=0, sumZ=0, count=0; 
    foreach (var pt in points) 
    { 
     sumX += pt.X; 
     sumY += pt.Y; 
     sumZ += pt.Z; 
     count++; 
    } 
    // also calc average time? 
    if (count == 0) 
    return new DataPoint(); 
    return new DataPoint {X=sumX/count,Y=sumY/count,Z=sumZ/count}; 
} 
} 
+0

Выглядит хорошо. Следующий глупый вопрос, как мне его назвать? –

+0

@Tom Wright - Points.Average() – Gleno

+0

@Gleno Вот что я подумал - попробую еще раз ... –

0

Ну, казалось бы, что вам нужно взять среднее значение каждого выступа в свою очередь

DataPoint averagePoint = new DataPoint{ 
      X = (int)Points.Average(p => X), 
      Y = (int)Points.Average(p => P.Y), 
      Z = (int)Points.Average(p => p.Z), 
      time = (int)Points.Average(p => p.time) 
      }; 

Я бросаю в целое, потому что ваши типы int, хотя они, вероятно, должны быть double, или преобразованы к вашей целочисленной решетке более разумно.

Другой способ - использовать скользящие средние значения. Это медленнее, чем Lamperts, и предполагает, что тип данных поддержки для DataPoint удваивается. Но если множество точек ОГРОМНО, а точки упорядочены случайным образом, у него хорошая сходимость Монте-Карло. Он также enumarates List только один раз .:

var averagePoint = Points.First(); 
foreach(var point in Points.Skip(1).Select((p,i) => new{ Point = p, Index = i})){ 
      averagePoint.X = (point.Index * averagePoint.X + p.Point.X)/(point.Index + 1); 
      averagePoint.Y = (point.Index * averagePoint.Y + p.Point.Y)/(point.Index + 1); 
      averagePoint.Z = (point.Index * averagePoint.Z + p.Point.Z)/(point.Index + 1); 
} 
+1

Основным недостатком этого подхода является то, что он перечисляет всю коллекцию четыре раза (один раз для каждого усредненного параметра). –

+1

Это только медленнее, если скорость важна. :) – Gleno

6

Вопрос не совсем понятно, но это звучит как то, что вы хотите, это новая точка P, где PX среднее из всех X координат точек в список и т. д., да?

Общий способ решить проблему, как это разбить его:

Сначала преобразуем список точек в четырех списков целых чисел.

var times = from p in points select p.Time; 
var xs = from p in points select p.X; 
... and so on .. 

Или, если вы предпочитаете это обозначение:

var times = points.Select(p=>p.Time); 

Теперь вы можете усреднить эти:

double averageTime = times.Average(); 
double averageX = xs.Average(); 
... and so on ... 

и теперь у вас есть четыре значения - в два раза - это ты может использовать для построения средней точки. Конечно, вам придется преобразовать двойники в целые числа, используя любое округление, которое вы предпочитаете.

Однако есть специальная версия «Среднее», которая объединяет Select и Average в одну операцию. Вы можете просто сказать

double averageTime = points.Average(p=>p.Time); 

и сделать это за один шаг как для проекции, так и для среднего.

Нижняя сторона этого подхода, как некоторые отметили, состоит в том, что последовательность перечисляется четыре раза. Что, вероятно, не имеет большого значения, так как это список в памяти, но может быть более важным, если бы это был дорогостоящий запрос к базе данных.

Другим подходом было бы определить оператор сложения в вашем классе DataPoint (если в целом имеет смысл суммировать две точки, что может и не быть). Когда у вас есть оператор добавления, сумма суммы всех точек проста.

Если вы определяете оператор сложения или нет, вы можете использовать Aggregate для вычисления суммы всех точек, а затем разделить четыре поля суммы на количество точек.

DataPoint sum = points.Aggregate(
    new DataPoint(0, 0, 0, 0), 
    (agg, point)=> new DataPoint(agg.time + point.time, agg.x + point.x, ...)); 

или, если у вас есть оператор, просто:

DataPoint sum = points.Aggregate(
    new DataPoint(0, 0, 0, 0), 
    (agg, point)=> agg + point); 

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

 Смежные вопросы

  • Нет связанных вопросов^_^