如何编写一个扩展函数来返回自定义类型的平均值

本文关键字:返回 自定义 类型 平均值 函数 扩展 何编写 一个 | 更新日期: 2023-09-27 17:58:52

这可能很简单,但我的尝试(在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&z值,该值是组成列表的元素的这些属性的平均值。我该怎么做?我正在努力解决的问题是(我认为)someMagicHere,但我可能一开始就使用了完全错误的方法。

如何编写一个扩展函数来返回自定义类型的平均值

这个问题还不完全清楚,但听起来你想要的是一个新的点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 ...

现在你有了四个值,作为二重,可以用来构造平均点。当然,您必须使用您喜欢的任何四舍五入将双精度转换为整数。

但是,有一个特殊版本的"Average",它将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);

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

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

好吧,你似乎需要依次取每个投影的平均值

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