我正在基于点列表(例如距离,面积,质心等)实现一些数学算法。 那篇文章描述了如何通过本质上用“自身”压缩序列来计算点序列的总距离(按顺序),通过将原始IEnumerable的起始位置偏移1来生成Zip序列。
因此,给定.Net4.0中的Zip扩展名,假定Point为点类型,并且具有合理的Distance公式,您可以像这样进行调用以生成从一个点到下一个点的一系列距离,然后对距离求和:
var distances = points.Zip(points.Skip(1),Distance); double totalDistance = distances.Sum();
面积和质心计算相似,因为它们需要遍历序列,处理每对点(点[i]和点[i + 1])。我想到了使通用IEnumerable扩展适用于实现这些(以及可能其他)对序列进行操作的算法,一次获取两项(points [0]和points [1],points [1]和points [2], …,points [n-1]和points [n](或者是n-2和n-1 …)并应用函数。
我的通用迭代器将具有与Zip类似的签名,但是它不会收到第二个压缩序列,因为它实际上只是将自身压缩。
我的第一次尝试是这样的:
public static IEnumerable<TResult> ZipMyself<TSequence, TResult>(this IEnumerable<TSequence> seq, Func<TSequence, TSequence, TResult> resultSelector) { return seq.Zip(seq.Skip(1),resultSelector); }
开始编辑: 看到响应后,我已经实现了Pairwise并明确使用了底层枚举器,如下所示:
public static IEnumerable<TResult> Pairwise<TSequence, TResult>(this IEnumerable<TSequence> seq, Func<TSequence, TSequence, TResult> resultSelector) { TSequence prev = default(TSequence); using (IEnumerator<TSequence> e = seq.GetEnumerator()) { if (e.MoveNext()) prev = e.Current; while (e.MoveNext()) yield return resultSelector(prev, prev = e.Current); } }
虽然肯定比我的初始版本复杂,但是此迭代输入序列一次,而原始迭代两次。
结束编辑
有了通用迭代器,我可以编写如下函数:
public static double Length(this IEnumerable<Point> points) { return points.ZipMyself(Distance).Sum(); }
并这样称呼它:
double d = points.Length();
和
double GreensTheorem(Point p1, Point p1) { return p1.X * p2.Y - p1.Y * p2.X; } public static double SignedArea(this IEnumerable<Point> points) { return points.ZipMyself(GreensTheorem).Sum() / 2.0 } public static double Area(this IEnumerable<Point> points) { return Math.Abs(points.SignedArea()); } public static bool IsClockwise(this IEnumerable<Point> points) { return SignedArea(points) < 0; }
并这样称呼他们:
double a = points.Area(); bool isClockwise = points.IsClockwise();
在这种情况下,是否有任何理由不根据Zip和Skip(1)实现“ ZipMyself”?LINQ中是否已经有一些东西可以自动执行此操作(将列表本身压缩)-不需要使它变得那么容易;-)
另外,该扩展是否有更好的名称,可能反映出它是一个众所周知的模式(如果确实是一个众所周知的模式)?
在此处有一个有关面积计算的StackOverflow问题的链接。是问题2432428。
也有指向Wikipedia的有关Centroid的文章的链接。如果感兴趣,只需转到Wikipedia并搜索Centroid。
刚开始,所以没有足够的代表来发布多个链接。
开始编辑
为了完整起见,如果有人在搜索距离,面积或质心之后到达此处,则这是我的函数,它们接受位置类型的列表(假定对于面积和质心关闭),并返回该位置的距离(沿),面积和质心职位:
public struct Position { public double X; public double Y; static public double Distance(Position p1, Position p2) { double dx = p2.X - p1.X; double dy = p2.Y - p1.Y; return Math.Sqrt(dx*dx + dy*dy); } } public static class PointMath { public static double Distance(IEnumerable<Position> pts) { return pts.Pairwise((p1, p2) => Position.Distance(p1, p2)).Sum(); } private static bool IsClockwise(IEnumerable<Position> pts) { return SignedArea(pts) < 0; } private static double SignedArea(IEnumerable<Position> pts) { return pts.Pairwise((p1, p2) => (p1.X * p2.Y - p1.Y * p2.X)).Sum() / 2.0; } public static double Area(IEnumerable<Position> pts) { return Math.Abs(SignedArea(pts)); } public static Position Centroid(IEnumerable<Position> pts) { double a = SignedArea(pts); var c = pts.Pairwise((p1, p2) => new { x = (p1.X + p2.X) * (p1.X * p2.Y - p2.X * p1.Y), y = (p1.Y + p2.Y) * (p1.X * p2.Y - p2.X * p1.Y) }) .Aggregate((t1, t2) => new { x = t1.x + t2.x, y = t1.y + t2.y }); return new Position(1.0 / (a * 6.0) * c.x, 1.0 / (a * 6.0) * c.y); } }
随时发表评论。
是的- 也称为Pairwise。以前已经做过,例如在这里。在SO)之前,这里也有一个问题。
Pairwise
如您所指出的,成对现在可以根据.NET 4.0的Zip来实现。对于LINQ toObjects解决方案来说,这似乎是一种合理的方法,尽管在这一点上拥有适用于.NET v3.5的版本可能对更广泛的受众而言更有用。