Skip to content

Instantly share code, notes, and snippets.

@ashaindlin
Last active August 29, 2015 14:09
Show Gist options
  • Save ashaindlin/c93f7ea6cab773f97103 to your computer and use it in GitHub Desktop.
Save ashaindlin/c93f7ea6cab773f97103 to your computer and use it in GitHub Desktop.
Graham scan for convex hull calculation (Real World Haskell Ch. 3 Exercises 10-13)
module ConvexHull where
import Data.List (sortBy, delete)
import Data.Ord (comparing)
data Direction = Left | Right | Straight
deriving (Show, Eq)
data Point = Point {
x :: Double,
y :: Double
} deriving (Show, Eq)
-- Calculate the turn made by three 2D points
getDirection :: Point -> Point -> Point -> Direction
getDirection a b c
| turn > 0 = ConvexHull.Left
| turn == 0 = ConvexHull.Straight
| turn < 0 = ConvexHull.Right
where turn = (((x b)-(x a))*((y c)-(y a))) - (((y b)-(y a)))*(((x c)-(x a)))
-- Given a list of points, list the directions of turns for them
path :: [Point] -> [Direction]
path (a:b:c:ps) = (getDirection a b c):(path (b:c:ps))
path _ = []
-- Starts in the +x direction and sweeps out a complete counter-clockwise circle
angleFromX :: Point -> Double
angleFromX p
| angle >= 0 = angle
| otherwise = angle + 2*pi
where angle = atan2 (y p) (x p)
-- Sort points by angle from x-axis and duplicate the first one at end of list
preparePoints :: [Point] -> [Point]
preparePoints ps = sorted ++ [head sorted]
where sorted = sortBy (comparing angleFromX) ps
grahamScan :: [Point] -> [Point]
grahamScan shape = (head ps):(map snd $ filter turn $ zip (path ps) (trim ps))
where
ps = preparePoints shape
turn (d, _) = d /= ConvexHull.Right
trim xs = init $ drop 1 xs
Prelude> :l ConvexHull.hs
[1 of 1] Compiling ConvexHull ( ConvexHull.hs, interpreted )
Ok, modules loaded: ConvexHull.
*ConvexHull> let a = [(Point 1 1), (Point 2 1), (Point (-1) 2), (Point 1 (-1)), (Point 1 (-2)), (Point (-1) (-1))]
*ConvexHull> grahamScan a
[Point {x = 2.0, y = 1.0},Point {x = -1.0, y = 2.0},Point {x = -1.0, y = -1.0},Point {x = 1.0, y = -2.0}]
@ashaindlin
Copy link
Author

The definition of turn on line 43 allows collinear points to remain in the returned list of hull points. If you wanted to exclude collinear points from the hull, you could use turn (d, _) = d == ConvexHull.Left.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment