Me estoy poniendo en práctica algunos algoritmos matemáticos basados en listas de puntos, al igual distancia, área, centroide, etc. Al igual que en este post: Find the distance required to navigate a list of points using linqCómo Zip uno IEnumerable consigo mismo
Ese puesto se describe cómo calcular el total distancia de una secuencia de puntos (tomados en orden) comprimiendo esencialmente la secuencia "consigo mismo", generando la secuencia para Zip compensando la posición inicial del IEnumerable original por 1.
Así que, dada la extensión Zip en. Net 4.0, asumiendo el punto para el tipo de punto y una fórmula de distancia razonable, puede hacer llamadas como esta para generar una secuencia de distancias de un punto al siguiente y luego sumar el Distancias:
var distances = points.Zip(points.Skip(1),Distance);
double totalDistance = distances.Sum();
de Área y cálculos Centroide son similares en que ellos necesitan para iterar sobre la secuencia, el procesamiento de cada par de puntos (puntos [i] y puntos [i + 1]). Pensé en hacer una extensión genérica de IEnumerable adecuada para implementar estos (y posiblemente otros) algoritmos que operan sobre secuencias, tomando dos elementos a la vez (puntos [0] y puntos [1], puntos [1] y puntos [2], ..., puntos [n-1] y puntos [n] (o es n-2 y n-1 ...) y aplicando una función
Mi iterador genérico tendría una firma similar a Zip, pero no recibiría una segunda secuencia de la cremallera con la que en realidad es sólo va a comprimir con ella misma
Mi primer intento es el siguiente:.
public static IEnumerable<TResult> ZipMyself<TSequence, TResult>(this IEnumerable<TSequence> seq, Func<TSequence, TSequence, TResult> resultSelector)
{
return seq.Zip(seq.Skip(1),resultSelector);
}
Comenzar edición: Después de ver las respuestas, he implementado por parejas con el uso explícito del enumerador subyacente de esta manera:
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);
}
}
Aunque ciertamente más complicado que mi versión inicial, éste se itera a través de la secuencia de entrada una vez, mientras que los de las iteraciones originales dos veces.
Fin editar
Con mi iterador genérico en su lugar, puede escribir funciones como este:
public static double Length(this IEnumerable<Point> points)
{
return points.ZipMyself(Distance).Sum();
}
y lo llaman así:
double d = points.Length();
y
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;
}
y llamarlos así:
double a = points.Area();
bool isClockwise = points.IsClockwise();
En este caso, ¿hay alguna razón para no aplicar "ZipMyself" en términos de Zip y Salta (1)? ¿Hay algo en LINQ que lo automatice (comprimir una lista consigo mismo)? No es que necesite ser mucho más fácil ;-)
Además, ¿hay mejor nombre para la extensión que pueda reflejar que es un patrón bien conocido (si, de hecho, es un patrón bien conocido)?
Tenía un enlace aquí para una pregunta de StackOverflow sobre el cálculo del área. Es la pregunta 2432428.
También tenía un enlace al artículo de Wikipedia sobre Centroide. Simplemente vaya a Wikipedia y busque Centroide si está interesado.
Recién comenzando, por lo que no hay suficientes representantes para publicar más de un enlace.
Comenzar edición
Para completar, si alguien llega aquí después de buscar distancia, área, o Centroide, aquí están mis funciones que aceptan una lista de tipos de posición (que se supone cerrado por Área y Centroide) y el retorno la Distancia (a lo largo), el Área y el Centroide de las Posiciones:
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);
}
}
Siéntase libre de comentar.
Fin de edición
Estoy aceptando esto como la respuesta, principalmente porque para el término Pairwise y para vincular a las referencias de Pairwise. Había visto el enlace de Pairwise aquí en StackOverflow, pero no lo encontré durante mis búsquedas sobre este tema. Notaré aquí, como lo hice cuando respondí a Gideon Engelberth, que la implementación de la manera que describí anteriormente hace que la entrada IEnumerable se repita dos veces, lo que supongo que podría ser costoso dependiendo de lo que el IEnumerable o los upstream tengan que hacer para generar la respuesta. – wageoghe