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