一尘不染

如何自己压缩一个IEnumerable

algorithm

我正在基于点列表(例如距离,面积,质心等)实现一些数学算法。
那篇文章描述了如何通过本质上用“自身”压缩序列来计算点序列的总距离(按顺序),通过将原始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);
  }
}

随时发表评论。

结束编辑


阅读 228

收藏
2020-07-28

共1个答案

一尘不染

另外,该扩展是否有更好的名称,可能反映出它是一个众所周知的模式(如果确实是一个众所周知的模式)?

是的-
也称为Pairwise。以前已经做过,例如在这里。在SO)之前,这里也有一个问题。

如您所指出的,成对现在可以根据.NET 4.0的Zip来实现。对于LINQ toObjects解决方案来说,这似乎是一种合理的方法,尽管在这一点上拥有适用于.NET v3.5的版本可能对更广泛的受众而言更有用。

2020-07-28