Как застегнуть один IEnumerable с собой - PullRequest
7 голосов
/ 05 мая 2010

Я реализую некоторые математические алгоритмы, основанные на списках точек, таких как Расстояние, Площадь, Центроид и т. Д. Как и в этом посте: Найдите расстояние, необходимое для навигации по списку точек, используя linq

В этом посте описывается, как рассчитать общее расстояние последовательности точек (взятых по порядку), по сути, сжав последовательность «с собой», сгенерировав последовательность для Zip, сместив начальную позицию исходного IEnumerable на 1.

Итак, учитывая расширение Zip в .Net 4.0, принимая точку для типа точки и разумную формулу расстояния, вы можете делать такие вызовы, чтобы сгенерировать последовательность расстояний от одной точки до следующей, а затем суммировать расстояния:

var distances = points.Zip(points.Skip(1),Distance);
double totalDistance = distances.Sum();

Расчеты площади и центроида схожи в том, что они должны выполнять итерацию по последовательности, обрабатывая каждую пару точек (точки [i] и точки [i + 1]). Я подумал о создании общего расширения IEnumerable, подходящего для реализации этих (и, возможно, других) алгоритмов, которые работают над последовательностями, принимая два элемента за раз (точки [0] и точки [1], точки [1] и точки [2], ..., точки [n-1] и точки [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();

В этом случае, есть ли причина НЕ использовать ZipMyself в терминах Zip и Skip (1)? В 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);
  }
}

Не стесняйтесь комментировать.

Конец редактирования

Ответы [ 2 ]

7 голосов
/ 05 мая 2010

Кроме того, есть ли лучшее название для расширения, которое может отражать то, что это хорошо известный шаблон (если, действительно, это широко известный шаблон)?

Да - он также известен как Pairwise. Это было сделано раньше, например здесь . Там также был вопрос об этом до здесь на SO .

Pairwise теперь может быть реализован в терминах Zip для .NET 4.0, как вы указали. Это кажется разумным подходом для решения LINQ to Objects, хотя наличие версии, которая работает и на .NET v3.5, вероятно, более полезно для более широкой аудитории.

3 голосов
/ 05 мая 2010

Когда я делал что-то подобное, я называл это SelectWithPrevious и имел версию, которая имела перегрузки как для «SelectWithPreviousItem» (занимал Func<TSource, TSource, TResult>), так и «SelectWithPreviousResult» (занимал Func<TResult, TSource, TResult>).

Кроме того, я реализовал это путем непосредственного сохранения последнего элемента, а не повторения последовательности дважды, как это делает подход Zip. Никогда не использовав LINQ-to-SQL, я не могу сказать наверняка, но мне интересно, если подход Zip / Skip совершает две поездки на сервер, чтобы дважды оценить запрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...