2010-05-04 19 views
7

我實現基於點的名單一些數學算法,如距離,面積,重心等就像在這個帖子:Find the distance required to navigate a list of points using linq如何郵編一個IEnumerable的自身

該職位介紹如何計算總通過基本上將序列「自身」壓縮以產生Zip序列,通過將原始IEnumerable的開始位置偏移1來排列點序列(按順序排列)的距離。

因此,給定Zip擴展。 Net 4.0,假設點類型的點和合理的距離公式,您可以調用這樣的方式來生成從一點到下一點的距離序列,然後求和距離:

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

面積和質心計算是因爲它們需要遍歷序列,處理每對點相似(點[i]和點第[i + 1])。我想要製作一個通用的IEnumerable擴展,它適用於實現這些(也可能是其他)算法,這些算法在序列上運行,一次只處理兩個項目(點[0]和點[1],點[1]和點[2]), ...,點[n-1]和點[n](或者它是n-2和n-1 ...)並應用一個函數

我的泛型迭代器會有一個類似於Zip的簽名,但它不會收到第二個序列與因爲它真的只是打算以自己拉鍊拉上

我的第一次嘗試是這樣的:

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

開始編輯:通過輸入序列,而原來的迭代

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

雖然比我最初的版本肯定更加複雜,這一個迭代一次: 看到的答覆後,我已經實現配對,像這樣明確使用底層枚舉兩次。

編輯完

與我的地方通用的迭代,我可以寫這樣的功能:

public static double Length(this IEnumerable<Point> 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<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; 
} 

,並呼籲他們這樣的:

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

在這種情況下,沒有任何理由不以郵編方面實行「ZipMyself」和Skip(1)? LINQ中是否已經有一些東西可以自動化(自己拉列表) - 並不是說​​它需要變得更容易;-)

此外,有沒有更好的擴展名稱可能反映它是一個衆所周知的模式(如果它確實是一個衆所周知的模式)?

有關於面積計算StackOverflow問題的鏈接。這是問題2432428。

也有一個鏈接到Centroid的維基百科文章。只要去維基百科搜索Centroid,如果感興趣。

剛開始,所以沒有足夠的代表發佈多個鏈接。

開始編輯

爲了完整起見,如果有人搜索距離,面積,或重心之後送過來,這裏有我的函數接受(關閉面積和質心假設),並返回位置類型的列表位置的距離(沿),面積和質心:

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

隨時發表評論。

編輯完

回答

7

而且,是有可能會反映,這是一個衆所周知的模式(如,的確是一個衆所周知的模式)擴展更好的名字?

是 - 它也被稱爲Pairwise。之前已經完成,例如here。在here on SO之前也有一個關於它的問題。

如您所指出的,Pairwise現在可以通過Zip for .NET 4.0實現。對於LINQ to Objects解決方案來說,這似乎是一個合理的方法,儘管在.NET v3.5上有一個版本可能對更廣泛的受衆更有用。

+0

我接受這個答案,主要是因爲對於術語Pairwise和鏈接到Pairwise引用。我在StackOverflow上看到了Pairwise鏈接,但在搜索這個問題時我沒有遇到它。我會在這裏注意到,正如我在回覆Gideon Engelberth時所做的那樣,實現上述方式確實會導致輸入IEnumerable被迭代兩次,根據IEnumerable或上游必須執行的操作,我認爲這可能會很昂貴生成答案。 – wageoghe 2010-05-05 19:44:49

3

當我做類似的東西,我把它叫做SelectWithPrevious,有一個版本,有兩個「SelectWithPreviousItem」過載(花了Func<TSource, TSource, TResult>)和「SelectWithPreviousResult」(花了Func<TResult, TSource, TResult>)。

另外,我通過直接存儲最後一個元素來實現它,而不是像Zip方法那樣迭代兩次序列。從未使用LINQ到SQL,我不能肯定地說,但我想知道Zip/Skip方法是否使服務器兩次訪問兩次來評估查詢。

+0

這是一個關於Zip/Skip多次訪問服務器的好問題,我不知道答案。在我的情況下,這些操作通常會(總是)針對對象執行。我使用硬編碼的yield return語句編寫了一個ZipMyself擴展的簡單測試。由於兩個序列是並行迭代的,因此每次產量回報都達到了兩次。當我用一個使用Enumerator的Pairwise迭代器進行測試時,它直接按照你所描述的方式進行測試,因爲我只是實際上迭代一次序列,所以它每次都會返回一個yield return。 – wageoghe 2010-05-05 19:39:17