How to Zip one IEnumerable with itself(如何用自己压缩一个 IEnumerable)
问题描述
I am implementing some math algorithms based on lists of points, like Distance, Area, Centroid, etc. Just like in this post: Find the distance required to navigate a list of points using linq
That post describes how to calculate the total distance of a sequence of points (taken in order) by essentially zipping the sequence "with itself", generating the sequence for Zip by offsetting the start position of the original IEnumerable by 1.
So, given the Zip extension in .Net 4.0, assuming Point for the point type, and a reasonable Distance formula, you can make calls like this to generate a sequence of distances from one point to the next and then to sum the distances:
var distances = points.Zip(points.Skip(1),Distance);
double totalDistance = distances.Sum();
Area and Centroid calculations are similar in that they need to iterate over the sequence, processing each pair of points (points[i] and points[i+1]). I thought of making a generic IEnumerable extension suitable for implementing these (and possibly other) algorithms that operate over sequences, taking two items at a time (points[0] and points[1], points[1] and points[2], ..., points[n-1] and points[n] (or is it n-2 and n-1 ...) and applying a function.
My generic iterator would have a similar signature to Zip, but it would not receive a second sequence to zip with as it is really just going to zip with itself.
My first try looks like this:
public static IEnumerable<TResult> ZipMyself<TSequence, TResult>(this IEnumerable<TSequence> seq, Func<TSequence, TSequence, TResult> resultSelector)
{
return seq.Zip(seq.Skip(1),resultSelector);
}
Begin edit: After seeing the responses, I have implemented Pairwise with explicit use of the underlying Enumerator like this:
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);
}
}
While certainly more complicated than my initial version, this one iterates through the input sequence once whereas the the original iterates twice.
End edit
With my generic iterator in place, I can write functions like this:
public static double Length(this IEnumerable<Point> points)
{
return points.ZipMyself(Distance).Sum();
}
and call it like this:
double d = points.Length();
and
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;
}
and call them like this:
double a = points.Area();
bool isClockwise = points.IsClockwise();
In this case, is there any reason NOT to implement "ZipMyself" in terms of Zip and Skip(1)? Is there already something in LINQ that automates this (zipping a list with itself) - not that it needs to be made that much easier ;-)
Also, is there better name for the extension that might reflect that it is a well-known pattern (if, indeed it is a well-known pattern)?
Had a link here for a StackOverflow question about area calculation. It is question 2432428.
Also had a link to Wikipedia article on Centroid. Just go to Wikipedia and search for Centroid if interested.
Just starting out, so don't have enough rep to post more than one link.
Begin edit
For completeness, if anyone gets here after searching for Distance, Area, or Centroid, here are my functions that accept a list of Position types (assumed closed for Area and Centroid) and return the Distance(along), Area, and Centroid of the Positions:
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);
}
}
Feel free to comment.
End edit
Also, is there better name for the extension that might reflect that it is a well-known pattern (if, indeed it is a well-known pattern)?
Yes - it is also known as Pairwise
. It has been done before, for example here. There also has been a question about it before here on SO.
Pairwise can now be implemented in terms of Zip for .NET 4.0, as you point out. This seems like a reasonable approach for a LINQ to Objects solution, although having a version that works on .NET v3.5 too is probably more useful to a wider audience at this point.
这篇关于如何用自己压缩一个 IEnumerable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:如何用自己压缩一个 IEnumerable
- 是否可以在 .Net 3.5 中进行通用控件? 2022-01-01
- Windows 喜欢在 LINUX 中使用 MONO 进行服务开发? 2022-01-01
- 为什么 C# 中的堆栈大小正好是 1 MB? 2022-01-01
- Azure Active Directory 与 MVC,客户端和资源标识同一 2022-01-01
- 使用 rss + c# 2022-01-01
- 带问号的 nvarchar 列结果 2022-01-01
- C# 通过连接字符串检索正确的 DbConnection 对象 2022-01-01
- 在 LINQ to SQL 中使用 contains() 2022-01-01
- 在 C# 中异步处理项目队列 2022-01-01
- CanBeNull和ReSharper-将其用于异步任务? 2022-01-01