1
+ -- import Data.List
2
+ -- import Data.Tuple
3
+
4
+ -- type Point = (Double, Double)
5
+
6
+ -- hull :: [Point] -> [Point]
7
+ -- hull l = filterPoints (orderByPolarAngel (findStart l) l)
8
+
9
+ -- filterPoints :: [Point] -> [Point]
10
+ -- filterPoints (x:y:z:xs)
11
+ -- | ccw x y z = x : filterPoints (y : z : xs)
12
+ -- | otherwise = x : filterPoints (z : xs)
13
+ -- filterPoints x = x
14
+
15
+ -- ccw :: Point -> Point -> Point -> Bool
16
+ -- ccw (x1, y1) (x2, y2) (x3, y3) = (x2 - x1)*(y3 - y1) - (y2 - y1)*(x3 - x1) >= 0
17
+
18
+ -- orderByPolarAngel :: Point -> [Point] -> [Point]
19
+ -- orderByPolarAngel p = sortBy (polarAngelCompare p)
20
+ -- where polarAngelCompare = \ f p1 p2 -> compare (polarAngel f p2) (polarAngel f p1)
21
+ -- polarAngel = \ (x1, y1) (x2, y2) -> (x2-x1)/(y2-y1)
22
+
23
+ -- findStart :: [Point] -> Point
24
+ -- findStart = swap . minimum . map swap
25
+
26
+
27
+ import Text.Printf
28
+ import Data.List
29
+
30
+ type Point = (Double , Double )
31
+
32
+ -- Euclidean distance
33
+ dist :: (Double , Double ) -> (Double , Double ) -> Double
34
+ dist (x1, y1) (x2, y2) = sqrt (f x1 x2 + f y1 y2)
35
+ where f a b = (a - b)^ 2
36
+
37
+ -- Use Graham scan, https://en.wikipedia.org/wiki/Graham_scan
38
+ -- Three points are a counter-clockwise turn if ccw > 0, clockwise if
39
+ -- ccw < 0, and collinear if ccw = 0 because ccw is a determinant that
40
+ -- gives twice the signed area of the triangle formed by p1, p2 and p3.
41
+ ccv :: Num a => (a , a ) -> (a , a ) -> (a , a ) -> a
42
+ ccv (x1, y1) (x2, y2) (x3, y3) = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)
43
+
44
+ -- Find bottom-most point taking into account the x coordinate
45
+ findMinYPoint :: [Point ] -> Point
46
+ findMinYPoint (p: points) = f p points
47
+ where
48
+ f prev [] = prev
49
+ f p0@ (x0,y0) (p1@ (x1,y1): points) | y1 < y0 = f p1 points
50
+ -- NB: select point with smaller x if y's are equal
51
+ | (y1 == y0) && (x1 < x0) = f p1 points
52
+ | otherwise = f p0 points
53
+
54
+ filterSameAngle :: [(Point , Double )] -> [Point ]
55
+ filterSameAngle lst = map fst . filter snd $ r
56
+ where
57
+ r = zipWith (\ (pa, ra) (pb, rb) -> (pa, ra /= rb)) lst (drop 1 lst ++ [head lst])
58
+
59
+ pretty :: [Point ] -> String
60
+ pretty = unlines . map h
61
+ where h (x, y) = printf " \t %0.f %.0f" x y
62
+
63
+ build :: [Point ] -> [Point ] -> [Point ]
64
+ build hull [] = hull
65
+ build hs@ [h1] ps@ (p: points) = build ([p, h1]) points
66
+ build hs@ (h2: h1: hull) ps@ (p: points) = hull'
67
+ where
68
+ rightTurn = ccv h1 h2 p < 0
69
+ collinear = ccv h2 h1 p == 0
70
+ hull' | rightTurn = build (h1: hull) ps -- Remove head and retry
71
+ | collinear = build (p: h1: hull) points -- Replace the head with p
72
+ | otherwise = build (p: hs) points -- Add p
73
+
74
+ convexHull :: [(Double , Double )] -> [(Double , Double )]
75
+ convexHull points = hull
76
+ where
77
+ -- 1) Find the bottom-most point
78
+ p0@ (x0, y0) = findMinYPoint points
79
+
80
+ -- 2) Sort in increasing order of the angle they and the point P make with the x-axis
81
+ sorted' = let
82
+ o (a, ra) (b, rb) | ra > rb = GT
83
+ -- NB: Nearest points first, further points GT
84
+ | (ra == rb) && (dist a p0 > dist b p0) = GT
85
+ | otherwise = LT
86
+ in sortBy o hullP
87
+
88
+ -- For performance, instead of atan, do not calculate the
89
+ -- angle. Use e.g. cos for ordering intead
90
+ f (x, y) = r'
91
+ where r = atan $ (y - y0) / (x - x0)
92
+ -- NB: Avoid negative angles values
93
+ r' | r < 0 = r + 2 * pi
94
+ | otherwise = r
95
+
96
+ hullP = map (\ p -> (p, f p)) $ delete p0 points
97
+
98
+ -- 3) NB: Remove points with same angles that are closer from p0
99
+ sorted = filterSameAngle sorted'
100
+
101
+ -- 4) Recursively build the convex hull
102
+ hull = build [p0] sorted
103
+
104
+ distance p1 p2 = sqrt ((x2 - x1) ** 2 + (y2 - y1) ** 2 )
105
+ where x1 = fst p1
106
+ x2 = fst p2
107
+ y1 = snd p1
108
+ y2 = snd p2
109
+
110
+ repeatFirst x = take (length x + 1 ) (cycle x)
111
+
112
+ findDist :: [(Double , Double )] -> [Double ]
113
+ findDist [] = []
114
+ findDist [x] = []
115
+ findDist (x: xs) = distance x (head xs) : findDist xs
116
+
117
+ -- makes pair of the alternative elements in the list
118
+ makeP :: [Double ] -> [Point ]
119
+ makeP (x: y: rest) = (x,y) : makeP rest
120
+ makeP _ = []
121
+
122
+ cases = " 6 \
123
+ \1 1 \
124
+ \2 5 \
125
+ \3 3 \
126
+ \5 3 \
127
+ \3 2 \
128
+ \2 2"
129
+
130
+ solve :: String -> IO ()
131
+ solve = printf " %.1f\n " . sum . findDist . repeatFirst . convexHull . makeP . map read . tail . words
132
+
133
+ main = getContents >>= solve
0 commit comments