2011-06-09 54 views
1

这可能非常简单,但我的尝试(由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,但我可能完全用错了方法开始。

+0

你的变量名不应该以大写字母开头。该样式仅用于类型和方法。即“点”而不是“点”。 – 2011-06-09 20:21:18

+0

啊,是的,对不起,在写这个问题时马虎。将更正。 – 2011-06-09 20:24:21

回答

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

看起来不错。下一个愚蠢的问题,我怎么称呼它? – 2011-06-09 21:20:51

+0

@汤姆赖特 - Points.Average() – Gleno 2011-06-09 21:33:29

+0

@Gleno这就是我的想法 - 将再次尝试... – 2011-06-09 21:34:34

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

这种方法的主要缺点是它枚举了整个集合四次(每个参数被平均一次)。 – 2011-06-09 20:30:36

+1

速度很重要时,它只会变慢。 :) – Gleno 2011-06-09 20:33:46

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

现在你有总和,所以计算平均值是直截了当的。

相关问题