Я реализую некоторые математические алгоритмы на основе списков точек, как Расстояние, область, Центроид, и т.д. Точно так же, как в этом сообщении: Найдите расстояние требуемым переместиться по списку точек с помощью linq
То сообщение описывает, как вычислить общее расстояние последовательности точек (взятый в порядке) путем важного архивирования последовательности "с собой", генерации последовательности для Zip путем возмещения положения запуска исходного IEnumerable 1.
Так, учитывая расширение Zip в.Net 4.0, принимая Точку для типа точки и разумную формулу Расстояния, можно выполнить вызовы как это, чтобы генерировать последовательность расстояний от одной точки до следующего и затем суммировать расстояния:
var distances = points.Zip(points.Skip(1),Distance);
double totalDistance = distances.Sum();
Область и Центроидные вычисления подобны в этом, они должны выполнить итерации по последовательности, обработав каждую пару точек (точки [я] и точки [i+1]). Я думал о создании универсального расширения IEnumerable, подходящего для реализации их (и возможно другой) алгоритмы, которые работают по последовательностям, беря два объекта за один раз (точки [0] и точки [1], точки [1] и точки [2]..., точки [n-1] и точки [n] (или это n-2 и n-1...), и применение функции.
Мой универсальный итератор имел бы подобную подпись для Архивирования, но он не получит вторую последовательность для архивирования с тем, поскольку он действительно просто собирается архивировать с собой.
Моя первая попытка похожа на это:
public static IEnumerable ZipMyself(this IEnumerable seq, Func resultSelector)
{
return seq.Zip(seq.Skip(1),resultSelector);
}
Начните редактирование: После наблюдения ответов я реализовал Попарно с конкретным видом использования базового Перечислителя как это:
public static IEnumerable Pairwise(this IEnumerable seq, Func resultSelector)
{
TSequence prev = default(TSequence);
using (IEnumerator 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 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 points)
{
return points.ZipMyself(GreensTheorem).Sum() / 2.0
}
public static double Area(this IEnumerable points)
{
return Math.Abs(points.SignedArea());
}
public static bool IsClockwise(this IEnumerable points)
{
return SignedArea(points) < 0;
}
и назовите их как это:
double a = points.Area();
bool isClockwise = points.IsClockwise();
В этом случае разве там какая-либо причина НЕ состоит в том, чтобы реализовать "ZipMyself" с точки зрения Zip и Пропуска (1)? Уже есть ли что-то в LINQ, который автоматизирует это (архивирование списка с собой) - не, что это должно быть сделано этим намного легче ;-)
Кроме того, есть ли лучшее название расширения, которое могло бы отразить, что это - известный шаблон (если, действительно это - известный шаблон)?
Имел ссылку здесь для вопроса о StackOverflow о вычислении области. Это - вопрос 2432428.
Также имел ссылку на статью Wikipedia о Центроиде. Просто перейдите к Википедии и поиску Центроида, если заинтересовано.
Только начав, не имейте достаточного количества представителя для регистрации больше чем одной ссылки.
Начните редактирование
Для полноты, если кто-либо добирается здесь после поиска Расстояния, области или Центроида, вот мои функции, которые принимают список типов Положения (принятый закрытый для области и Центроида) и возвращают Расстояние (вперед), область и Центроид Положений:
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 pts)
{
return pts.Pairwise((p1, p2) => Position.Distance(p1, p2)).Sum();
}
private static bool IsClockwise(IEnumerable pts)
{
return SignedArea(pts) < 0;
}
private static double SignedArea(IEnumerable pts)
{
return pts.Pairwise((p1, p2) => (p1.X * p2.Y - p1.Y * p2.X)).Sum() / 2.0;
}
public static double Area(IEnumerable pts)
{
return Math.Abs(SignedArea(pts));
}
public static Position Centroid(IEnumerable 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);
}
}
Не стесняйтесь комментировать.
Редактирование конца
Также, есть ли лучшее название для расширения, которое могло бы отразить, что это хорошо известный паттерн (если, действительно, это хорошо известный паттерн)?
Да - он также известен как Pairwise
. Это делалось и раньше, например здесь. Также был вопрос об этом раньше здесь на SO.
Теперь, как вы указываете, парный метод может быть реализован в терминах Zip для .NET 4.0. Это кажется разумным подходом для решения LINQ to Objects, хотя наличие версии, работающей и на .NET v3.5, вероятно, более полезно для более широкой аудитории на данный момент.
Когда я сделал что-то подобное, я он назывался SelectWithPrevious
и имел версию с перегрузками для SelectWithPreviousItem (использовала Func
) и «SelectWithPreviousResult» (использовала Func
).
Кроме того, я реализовал это, напрямую сохранив последний элемент, а не повторяя последовательность дважды, как это делает подход Zip. Я никогда не использовал LINQ-to-SQL, я не могу сказать наверняка, но мне интересно, совершает ли подход Zip / Skip два обращения к серверу, чтобы дважды оценить запрос.