如何编写扩展函数以返回自定义类型的平均值?

时间:2011-06-09 20:19:44

标签: c# extension-methods average

这可能非常简单,但我的尝试(由Intellisense和MSDN引导)都不合适。

如果我有一个包含3个double的类,我怎么能得到这些列表的平均值?

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包含timexy&amp; z值是构成列表的元素的这些属性的平均值。我该怎么做呢?我正在努力的一点是(我认为)someMagicHere,但我可能完全采用错误的方法开始。

3 个答案:

答案 0 :(得分:6)

问题并不完全清楚,但听起来你想要的是一个新的点P,其中P.X是列表中各点的所有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 ...

现在你有四个值 - 双倍 - 你可以用来构建平均点。当然,你必须使用你喜欢的任何四舍五入将双打转换为整数。

但是,有一个特殊版本的“平均值”,它将“选择”和“平均”组合成一个操作。你可以说

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

现在你有了总和,所以计算平均值很简单。

答案 1 :(得分: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};
 }
}

答案 2 :(得分: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,因为你的类型是int,虽然它们可能应该是double,或者更智能地转换为整数格。

另一种方法是使用流动平均值。它比Lamperts慢,并假设DataPoint的支持数据类型是double。但是如果点集是巨大的,并且点是随机排序的,那么它具有很好的蒙特卡罗收敛性。它也只会使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);
}