使用F#的质心和多边形面积

时间:2014-11-06 04:17:41

标签: vb.net list map f#

我写了一个函数来找出多边形的面积和质心(基于这个参考:http://en.wikipedia.org/wiki/Centroid

但是,我对F#很新,我不知道如何将这个函数从VB.NET转换为F#(VS2010)。如果有人可以帮助我,那将是非常感激的(假设在F#中我已经有一个名为Point2D的类型,并且F#函数的输入是point2D的列表)。我的第一次尝试是在VB代码之下。但是我不喜欢我的版本,因为它必须逐步计算Cx,Cy和A - 这不是我的VB代码对应的真实反映

 Private Function Find_Centroid_And_Area(ByVal List As List(Of Point2D)) As AreaCentroid
     Dim result As New AreaCentroid()
     Try

        Dim Qx As Double = 0
        Dim Qy As Double = 0

        Dim A As Double = 0
        Dim Cx As Single = 0
        Dim Cy As Single = 0
        Dim P1 As Point2D = Nothing
        Dim P2 As Point2D = Nothing
        For i As Integer = 0 To List.Count - 1
           P1 = List(i)
           Select Case i
              Case List.Count - 1
                 P2 = List(0)
              Case Else
                 P2 = List(i + 1)
           End Select



           Dim Dx As Double = P2.X - P1.X
           Dim Dy As Double = P2.Y - P1.Y
           Dim Lx As Double = P1.X + (Dx / 3)
           Dim Ly As Double = P1.Y + (Dy / 3)

           A += (Dx * (P1.Y + P2.Y)) / 2
           Qx += (Dx * ((P1.Y ^ 2) + (Dy * Ly))) / 2
           Qy -= (Dy * ((P1.X ^ 2) + (Dx * Lx))) / 2


        Next

        Cx = CSng(Qy / A)
        Cy = CSng(Qx / A)

        Dim Centroid As New Point2D(Cx, Cy)
        Dim Area As Double = System.Math.Abs(A)


        result.Area = Area
        result.Centroid = Centroid


     Catch ex As Exception

     End Try

     Return result
  End Function

这是我的尝试:

type Point2D =  
 struct 
    val X:float
    val Y:float
    new(x:float, y:float) = {X=x; Y=y}
 end

let PolygonCentroidArea (points: Point2D list) =    
   let length = List.length points
   match length < 3 with
   | true -> 
        let A = 0.0
        let Cx = (points |> List.map (fun p -> p.X) |> List.average)
        let Cy = (points |> List.map (fun p -> p.Y) |> List.average)
        (A, Point2D(Cx,Cy)) // returned value
   | false ->
      let TakeFirst2ItemInList (pointList : Point2D list) =
            let p1 = List.head pointList
            let tail = List.tail pointList
            let p2 = List.head tail
            let newList = List.tail tail
            (p1,p2,newList)

      let rec Area pointList = 
              match (List.length pointList) with
              | 0 -> 0.0
              | _ ->
                    let (p1,p2,newList) = TakeFirst2ItemInList pointList
                    (p1.X+p2.Y-p2.X*p1.Y) + Area newList

      let rec Cx pointList = 
             match (List.length pointList) with
             | 0 -> 0.0
             | _ ->
                    let (p1,p2,newList) = TakeFirst2ItemInList pointList
                    (p1.X+p2.X)*(p1.X*p2.Y-p2.X*p1.Y) + Cx newList

      let rec Cy pointList = 
             match (List.length pointList) with
             | 0 -> 0.0
             | _ ->
                    let (p1,p2,newList) = TakeFirst2ItemInList pointList
                    (p1.Y+p2.Y)*(p1.Y*p2.X-p2.Y*p1.X) + Cy newList

      let FinalArea = 1.0/2.0 * abs(Area points)
      let FinalCx = 1.0/(6.0*FinalArea) * Cx points
      let FinalCy = 1.0/(6.0*FinalArea) * Cy points

      (FinalArea, Point2D(FinalCx,FinalCy))

3 个答案:

答案 0 :(得分:1)

我以前从VB做过翻译,我的建议是第一个工作版本,其结构与原始VB代码相同:

type Point2D =
    struct 
        val X:float
        val Y:float
        new(x:float, y:float) = {X =x; Y=y}
    end

let rec PolygonCentroidArea (points: Point2D list) =
    let mutable Qx = 0.
    let mutable Qy = 0.
    let mutable A  = 0.

    let length = List.length points

    for i = 0 to length-1 do 
        let P1 = points.[i]
        let P2 = 
            if i = length - 1 then points.[0] 
            else points.[i + 1]

        let Dx = P2.X - P1.X
        let Dy = P2.Y - P1.Y
        let Lx = P1.X + (Dx / 3.)
        let Ly = P1.Y + (Dy / 3.)

        A <- A + (Dx * (P1.Y + P2.Y)) / 2.
        Qx <- Qx +  (Dx * (pown P1.Y 2 + Dy * Ly))  / 2.
        Qy <- Qy - ((Dy * (pown P1.X 2 + Dx * Lx))) / 2.

    let Cx = Qy / A
    let Cy = Qx / A

    (abs A, Point2D(Cx, Cy))

在这里,我们可以将函数重构为更多的F#-ish解决方案。可变和环可以转换为折叠。在这种情况下,我们使用列表中的两个连续元素,因此我们可以在某处使用Seq.pairwise,也可以像在答案中一样重新排列计算。 这是我的解决方案:

let PolygonCentroidArea (points: Point2D list) =       
    let f (a, qx, qy) (p1: Point2D,p2: Point2D) = 
        let area  = a  + p1.X * p2.Y - p1.Y * p2.X
        let centX = qx + (p1.X + p2.X) * (p1.X + p2.Y - p1.Y * p2.X)
        let centY = qy + (p1.Y + p2.Y) * (p1.X + p2.Y - p1.Y * p2.X)
        area, centX, centY
    let a, qx, qy = Seq.fold f (0., 0., 0.) (Seq.pairwise (points @ [points.Head]))
    abs a / 2., Point2D(qx / 6. / abs a, qy / 6. / abs a)

答案 1 :(得分:0)

基于人们的答案和想法,以及可读性和代码简洁性之间的平衡 - 这里我正在编辑我的原始解决方案 - 这可能有助于其他人寻找同一问题的解决方案:< / p>

type Point2D =
struct 
    val X:float
    val Y:float
    new(x,y) = {X=x;Y=y}
end

let PolygonCentroidArea (points: Point2D list) = 
    let Elemement ((p1:Point2D), (p2:Point2D)) = 
        let cross = p1.X * p2.Y - p1.Y * p2.X
        let A  = cross
        let Cx = cross * (p1.X+p2.X)
        let Cy = cross * (p1.Y+p2.Y)
        (A, Cx,Cy)

    let SumElement (a1,cx1,cy1) (a2,cx2,cy2) = (a1+a2,cx1+cx2,cy1+cy2)

    let (A ,Cx, Cy) = 
         points
         |> Seq.pairwise
         |> Seq.map Elemement
         |> Seq.fold SumElement  (0.,0.,0.)

    (abs A/2. ,Cx/(6.*abs A), Cy/(6.*abs A))

let points = [Point2D(1.,0.); Point2D(5.,0.); Point2D(5.,2.); Point2D(1.,2.); Point2D(1.,0.)]
let test = PolygonCentroidArea points
//val test : float * float * float = (8.0, 1.5, 0.5)

答案 2 :(得分:0)

虽然其他答案的要点完全可以接受,但我对这个错误的结果有点担心。让我建议引入一个可以执行求和的辅助类型,允许我们用更自然的fold替换非直观的sum

type Sum3 = internal Sum3 of float * float * float with
    static member Zero = Sum3(0., 0., 0.)
    static member (+) (Sum3(a, b, c), Sum3(x, y, z)) = Sum3(a + x, b + y, c + z)

let polygonCentroidArea points =
    let (Sum3(a, cx, cy)) =
        points @ [List.head points] 
        |> Seq.pairwise
        |> Seq.sumBy (fun ((x0, y0), (x1, y1)) ->
            let z = x0 * y1 - x1 * y0 in Sum3(z, (x0 + x1) * z, (y0 + y1) * z) )
    let a = abs a / 2. in a, cx / 6. / a, cy / 6. / a

polygonCentroidArea[0., 0.; 4., 0.; 4., 2.; 0., 2.]
// val it : float * float * float = (8.0, 2.0, 1.0)