【发布时间】:2011-02-15 15:57:41
【问题描述】:
我正在实现一些基于点列表的数学算法,例如距离、面积、质心等。就像在这篇文章中一样:Find the distance required to navigate a list of points using linq
那篇文章描述了如何计算点序列的总距离(按顺序排列),方法是通过“自身”压缩序列,通过将原始 IEnumerable 的起始位置偏移 1 来生成 Zip 序列。
因此,给定 .Net 4.0 中的 Zip 扩展,假设点类型为 Point,以及一个合理的距离公式,您可以进行这样的调用以生成从一个点到下一个点的距离序列,然后将距离:
var distances = points.Zip(points.Skip(1),Distance);
double totalDistance = distances.Sum();
面积和质心计算的相似之处在于它们需要遍历序列,处理每对点(points[i] 和 points[i+1])。我想制作一个通用的 IEnumerable 扩展,适用于实现对序列进行操作的这些(可能还有其他)算法,一次获取两个项目(点 [0] 和点 [1],点 [1] 和点 [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,并像这样显式使用了底层 Enumerator:
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);
}
}
欢迎评论。
结束编辑
【问题讨论】:
标签: c# .net linq algorithm geometry