如何编写一个扩展函数来返回自定义类型的平均值
本文关键字:返回 自定义 类型 平均值 函数 扩展 何编写 一个 | 更新日期: 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
包含time
、x
、y
&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);
}