2010-05-04 7 views
7

Io sono l'attuazione alcuni algoritmi matematici basati su liste di punti, come la distanza, area, Centroide, ecc Proprio come in questo post: Find the distance required to navigate a list of points using linqCome Zip uno IEnumerable con se stessa

quel post descrive come calcolare il totale distanza di una sequenza di punti (presa nell'ordine) essenzialmente zippando la sequenza "con se stessa", generando la sequenza per Zip annullando la posizione iniziale dell'IEnumerable originale di 1.

Quindi, data l'estensione Zip in. Net 4.0, assumendo il punto per il tipo di punto e una ragionevole formula di distanza, è possibile effettuare chiamate come questa per generare una sequenza di distanze da un punto all'altro e quindi sommare il distanze:

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

dintorni e calcoli Centroid sono simili in quanto necessitano per scorrere la sequenza, elaborazione di ogni coppia di punti (punti [i] e punti [i + 1]). Ho pensato di creare un'estensione IEnumerable generica adatta all'implementazione di questi (e possibilmente altri) algoritmi che operano su sequenze, prendendo due elementi alla volta (punti [0] e punti [1], punti [1] e punti [2], ..., punti [n-1] e punti [n] (oppure n-2 e n-1 ...) e applicazione di una funzione

Il mio iteratore generico avrebbe una firma simile a Zip, ma non avrebbe ricevuto una seconda sequenza di zip con quanto è in realtà solo andando a zip con se stessa

il mio primo tentativo è simile al seguente:.

public static IEnumerable<TResult> ZipMyself<TSequence, TResult>(this IEnumerable<TSequence> seq, Func<TSequence, TSequence, TResult> resultSelector) 
{ 
    return seq.Zip(seq.Skip(1),resultSelector); 
} 

Begin edit: Dopo aver visto le risposte, ho implementato a coppie con l'utilizzo esplicito del Enumerator sottostante in questo modo:

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); 
    } 
} 

Mentre certamente più complicata di quanto la mia versione iniziale, questa itera attraverso la sequenza di input, una volta, mentre le le itera originali due volte.

Fine funzioni di modifica

Con la mia iteratore generico luogo, posso scrivere come questo:

public static double Length(this IEnumerable<Point> points) 
{ 
    return points.ZipMyself(Distance).Sum(); 
} 

e chiamare in questo modo:

double d = points.Length(); 

e

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; 
} 

e li chiamano in questo modo:

double a = points.Area(); 
bool isClockwise = points.IsClockwise(); 

In questo caso, v'è alcuna ragione di non attuare "ZipMyself" in termini di Zip e Skip (1)? C'è già qualcosa in LINQ che lo automatizza (zippando un elenco con se stesso) - non che debba essere reso molto più facile ;-)

Inoltre, c'è un nome migliore per l'estensione che potrebbe riflettere che è un schema ben noto (se, in effetti, è un modello ben noto)?

Aveva un collegamento qui per una domanda StackOverflow sul calcolo dell'area. È la domanda 2432428.

Aveva anche un collegamento all'articolo di Wikipedia su Centroid. Basta andare su Wikipedia e cercare Centroid se interessati.

All'inizio, quindi non ho abbastanza rep per pubblicare più di un collegamento.

Inizia modifica

Per completezza, se qualcuno arriva qui dopo la ricerca di distanza, area, o Centroide, qui sono le mie funzioni che accettano un elenco dei tipi di posizione (assunto chiusa Area e Centroide per) e il ritorno la Distanza (lungo), Area e Centroid delle Posizioni:

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); 
    } 
} 

Sentitevi liberi di commentare.

Fine modifica

risposta

7

Inoltre, c'è una migliore nome per l'estensione che potrebbe riflettere sul fatto che si tratta di un modello ben noto (se, in effetti si tratta di un modello ben noto)?

Sì, è anche noto come Pairwise. È stato fatto prima, ad esempio here. C'è stata anche una domanda a riguardo prima dello here on SO.

Pairwise può ora essere implementato in termini di Zip per .NET 4.0, come si fa notare. Questo sembra un approccio ragionevole per una soluzione LINQ to Objects, anche se avere una versione che funziona su .NET v3.5 è probabilmente più utile a un pubblico più ampio a questo punto.

+0

Accetto questo come risposta, principalmente perché per il termine Pairwise e per il collegamento ai riferimenti Pairwise. Ho visto il collegamento Pairwise qui su StackOverflow, ma non l'ho incontrato durante le mie ricerche su questo problema. Prenderò nota qui, come ho fatto quando ho risposto a Gideon Engelberth, che implementando il modo in cui ho descritto sopra fa sì che l'input IEnumerable venga iterato due volte che suppongo possa essere costoso a seconda di ciò che l'IEnumerable o gli upstream devono fare per generare la risposta. – wageoghe

3

Quando ho fatto qualcosa di simile, ho chiamato SelectWithPrevious e aveva una versione che aveva sovraccarichi sia per "SelectWithPreviousItem" (ha preso una Func<TSource, TSource, TResult>) e "SelectWithPreviousResult" (ha preso un Func<TResult, TSource, TResult>).

Inoltre, l'ho implementato memorizzando direttamente l'ultimo elemento piuttosto che iterando la sequenza due volte come fa l'approccio Zip. Non ho mai usato LINQ-to-SQL, non posso dirlo con certezza, ma mi chiedo se l'approccio Zip/Skip faccia due viaggi sul server per valutare due volte una query.

+0

Questa è una buona domanda su Zip/Skip che fa più viaggi sul server e non conosco la risposta. Nel mio caso, queste operazioni saranno tipicamente (sempre?) Eseguite contro oggetti. Ho scritto un semplice test della mia estensione ZipMyself su una sequenza con dichiarazioni di rendimento hard-coded. Colpisce due volte ogni rendimento, dal momento che le due sequenze vengono ripetute in parallelo. Quando ho provato con un iteratore Pairwise usando l'Enumeratore direttamente come hai descritto, ha colpito ogni rendimento una volta dal momento che sto solo iterando la sequenza una sola volta. – wageoghe

Problemi correlati