我正在实施一些基于点列表的数学算法,例如距离,面积,质心等。就像在这篇文章中一样:Find the distance required to navigate a list of points using linq
该帖子描述了如何通过基本上压缩序列“自身”来计算一系列点的总距离(按顺序排列),通过将原始IEnumerable的起始位置偏移1来生成Zip的序列。 / p>
因此,考虑到.Net 4.0中的Zip扩展,假设点类型为Point,并且合理的距离公式,您可以进行这样的调用,以生成从一个点到下一个点的距离序列,然后对距离:
var distances = points.Zip(points.Skip(1),Distance);
double totalDistance = distances.Sum();
面积和质心计算类似,因为它们需要迭代序列,处理每对点(点[i]和点[i + 1])。我想过制作一个通用的IEnumerable扩展,适合实现这些(可能还有其他)算法,这些算法在序列上运行,一次取两个项目(points [0]和points [1],points [1]和points [2], ...,点[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.。
还有关于Centroid的维基百科文章的链接。如果感兴趣,只需去维基百科搜索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);
}
}
随意发表评论。
结束修改
答案 0 :(得分:7)
此外,扩展名是否有更好的名称可能反映出它是一个众所周知的模式(如果它确实是一个众所周知的模式)?
是的 - 它也被称为Pairwise
。之前已经完成了,例如here。在here on SO之前还有一个问题。
现在可以按照Zip for .NET 4.0实现Pairwise,正如您所指出的那样。对于LINQ to Objects解决方案来说,这似乎是一种合理的方法,尽管在.NET v3.5上运行的版本也可能对更广泛的用户更有用。
答案 1 :(得分:3)
当我做了类似的事情时,我称之为SelectWithPrevious
并且有一个版本对“SelectWithPreviousItem”(采用Func<TSource, TSource, TResult>
)和“SelectWithPreviousResult”(采用Func<TResult, TSource, TResult>
都有重载)。
另外,我通过直接存储最后一个元素而不是像Zip方法那样迭代序列两次来实现它。从来没有使用过LINQ-to-SQL,我不能肯定地说,但我想知道Zip / Skip方法是否两次访问服务器以评估查询两次。