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