2010-05-04 7 views
7

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

Respuesta

7

Además, ¿hay mejor nombre para la extensión que podría reflejar que se trata de un patrón bien conocido (si, de hecho se trata de un patrón bien conocido)?

Sí - también se conoce como Pairwise. Se ha hecho antes, por ejemplo here. También ha habido una pregunta al respecto antes de here on SO.

Ahora se puede implementar Pairwise en términos de Zip para .NET 4.0, como usted señala. Esto parece un enfoque razonable para una solución LINQ to Objects, aunque tener una versión que funcione también en .NET v3.5 probablemente sea más útil para una audiencia más amplia en este momento.

+0

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

3

Cuando hice algo similar, me llamó SelectWithPrevious y tenía una versión que tenía sobrecargas, tanto "SelectWithPreviousItem" (tomó un Func<TSource, TSource, TResult>) y "SelectWithPreviousResult" (tomó un Func<TResult, TSource, TResult>).

Además, lo implementé almacenando directamente el último elemento en lugar de iterar la secuencia dos veces como hace el enfoque Zip. Al no haber usado nunca LINQ-to-SQL, no puedo asegurarlo, pero me pregunto si el enfoque Zip/Skip hace dos viajes al servidor para evaluar una consulta dos veces.

+0

Esa es una buena pregunta sobre Zip/Skip teniendo múltiples viajes al servidor y no sé la respuesta. En mi caso, estas operaciones típicamente (¿siempre?) Se realizarán contra objetos. Escribí una prueba simple de mi extensión ZipMyself contra una secuencia con declaraciones de retorno de rendimiento codificadas. Golpeó cada retorno de rendimiento dos veces, ya que las dos secuencias se están iterando en paralelo. Cuando probé con un iterador Pairwise usando el Enumerator directamente como lo describiste, golpea cada retorno de rendimiento una vez ya que solo estoy iterando la secuencia una vez. – wageoghe

Cuestiones relacionadas