# Implementation of Graham Scan algorithm in Haskell

Here is an implementation of Graham Scan algorithm for computing the convex hull of a finite set of points:

data Point = Point {
xcoord :: Double,
ycoord :: Double
} deriving (Show)

data Direction = ToRight | Straight | ToLeft  deriving (Show, Eq)

distance :: Point -> Point -> Double
distance a b = sqrt (((xcoord a) - (xcoord b))^2 + ((ycoord a) - (ycoord b))^2)

-- The direction function can be simplified after some algebraic analysis:
direction :: Point -> Point -> Point -> Direction
direction a b c
| s < 0 = ToRight
| s == 0 = Straight
| s > 0 = ToLeft
where
s = ((xcoord c) - (xcoord a)) * ((ycoord a) - (ycoord b)) + ((ycoord c) - (ycoord a)) * ((xcoord b) - (xcoord a))

directions_ :: [Point] -> [Direction]
directions_ [] = []
directions_ [_] = []
directions_ [_, _] = []
directions_ (a:b:c:xs) = (direction a b c) : (directions_ (b:c:xs))

directions :: [Point] -> [Direction]
directions xs = ToLeft : ToLeft : (directions_ xs)

lowestYPoint :: [Point] -> Point
lowestYPoint [x] = x
lowestYPoint (x:xs)
| (ycoord x) <= (ycoord lowestRest) = x
| otherwise = lowestRest
where
lowestRest = lowestYPoint xs

cosineAB :: Point -> Point -> Double
cosineAB a b = ((xcoord b) - (xcoord a)) / (distance a b)

sortByAngle :: Point -> [Point] -> [Point]
sortByAngle x xs = sortBy comparePoints xs where
comparePoints :: Point -> Point -> Ordering
comparePoints a b
| cA > cB = LT
| cA < cB = GT
| otherwise = case () of
_ | xA < xB  -> LT
| xA > xB  -> GT
| xA == xB -> case () of
_ | yA < yB   -> LT
| yA > yB   -> GT
| yA == yB  -> EQ
where
yA = (ycoord a)
yB = (ycoord b)
xA = (xcoord a)
xB = (xcoord b)
cA = (cosineAB x a)
cB = (cosineAB x b)

grahamScanDirections :: [Direction] -> [Bool]
grahamScanDirections [] = []
grahamScanDirections [_] = [True]
grahamScanDirections (ToLeft:ToRight:xs) = False : (grahamScanDirections (ToLeft:xs))
grahamScanDirections (ToLeft:ToLeft:xs)  = True  : (grahamScanDirections (ToLeft:xs))
grahamScanDirections (ToLeft:Straight:xs) = False : (grahamScanDirections (ToLeft:xs))
grahamScanDirections _ = error "Impossible scenario"

filterByList :: [Bool] -> [a] -> [a]
filterByList [] _ = []
filterByList _ [] = []
filterByList (x:xs) (y:ys)
| x = y : (filterByList xs ys)
| otherwise = (filterByList xs ys)

grahamScan :: [Point] -> [Point]
grahamScan ps = filterByList bool_list ps1 where
-- sort ps by their angles (or cosine values) formed with the lowest point
ps1 = sortByAngle (lowestYPoint ps) ps

## ‡ Why I prefer pattern matching to guards

1. Precision
2. Efficiency
3. Consistency

Pattern matching repeats what is important about the value we're inspecting. The constructors and values we care about are directly on display, and the irrelevant portions are shown to be unimportant by naming them _. Consider the difference between these two fragments.

data Shape = Circle | Square | Rectangle Int Int
isSquare :: Shape -> Bool

func1 :: Shape -> a
func1 s | isSquare s = ...
| otherwise  = ...

func2 :: Shape -> a
func2 Square                      = ...
func2 _                           = ...

Now, are func1 and func2 equivalent? That depends on whether isSquare checks to see if you've been passed a Rectangle with equal length sides.

If you compile with -fwarn-incomplete-patterns GHC will tell you about partial pattern matches in your functions as well, but if you forget to include a guard you're on your own. E.g., my version of direction will not compile if you don't include the EQ case, your version will fail at runtime if you leave out s == 0.

In the example of direction, the guard version will perform three comparisons on s. The default definition of Ord defines all of the comparators (<, <=, &c) in terms of compare, so in the worst case you'll have to call compare three times to figure out which guard matches. In the case version compare is called only once and its result gets reused, potentially saving a lot of time if the implementation of compare were costly.

And lastly, having a set style and preference leads to consistency, which is always a boon to readability. I did it, therefore I do it.

• Note: though the last filterByList function is more elegant, it is unfortunately slower than the ugly one. Mar 4, 2015 at 7:27
• Do you have a benchmark for that? Because it seems to be the other way around. Here is a gist compiling both versions with -O2 -ddump-simpl, you can see the Core for the elegant version turns into a tidy little foldr. There's a little teeny benchmark in there too, the elegant version runs >5 times faster. Mar 4, 2015 at 19:40
• Here are my outputs pastebin.com/5yxFDHJD As you can see the cost centre of filterByList increases when using the cleaner version. Mar 5, 2015 at 8:04
• Dunno what to tell you, I'm using GHC 7.6.3 so it must be a compiler regression. I don't really understand your profiling output either because both reports reference graham.opt (lines 4 and 29) so it looks like you ran the same test twice. Mar 5, 2015 at 13:57
• You can download my tests here ouep.eu/pic/83125.zip just run make prof in the directory to compile and run the profiling. Concerning the profilings both saying graham.opt, it's only because I hand modified the same file to test the two functions. I am interested to see if it's a GHC regression from 7.6.3 to 7.8.3. Thanks :-) Mar 6, 2015 at 6:01

Here are some improvements you may apply.

You can combine pattern matching and records. It allows you to avoid unnecessary numerous calls to xcoord and ycoord. It also makes your code easier to read.

You can force strictness on xcoord and ycoord fields.

These two improvements help with performance.

You can also do the following:

• simplify pattern matching py reordering the patterns (see filterByList)
• drop lots of parenthesis since space has bigger precedence than operators
• simplify comparePoints with keeping only one level of guards
• use more standard library functions (see lowestYPoint)

Most of these were given to me by hlint ;-)

import Data.List
import Data.Function (on)
import Control.DeepSeq

data Point = Point {
xcoord :: !Double,
ycoord :: !Double
} deriving Show

data Direction = ToRight | Straight | ToLeft  deriving (Show, Eq)

distance :: Point -> Point -> Double
distance (Point xA yA) (Point xB yB) = sqrt ((xA - xB)^2 + (yA - yB)^2)

direction :: Point -> Point -> Point -> Direction
direction (Point xA yA) (Point xB yB) (Point xC yC)
| s <  0    = ToRight
| s == 0    = Straight
| otherwise = ToLeft
where
s = (xC - xA) * (yA - yB) + (yC - yA) * (xB - xA)

directions_ :: [Point] -> [Direction]
directions_ (a:b:c:xs) = direction a b c : directions_ (b:c:xs)
directions_ _ = []

directions :: [Point] -> [Direction]
directions xs = ToLeft : ToLeft : directions_ xs

lowestYPoint :: [Point] -> Point
lowestYPoint = minimumBy (compare on ycoord)

cosineAB :: Point -> Point -> Double
cosineAB a@(Point xA _) b@(Point xB _) = (xB - xA) / distance a b

comparePoints :: Point -> Point -> Point -> Ordering
comparePoints x a@(Point xA yA) b@(Point xB yB)
| cA > cB   = LT
| cA < cB   = GT
| xA < xB   = LT
| xA > xB   = GT
| yA < yB   = LT
| yA > yB   = GT
| otherwise = EQ
where
cA = cosineAB x a
cB = cosineAB x b

sortByAngle :: Point -> [Point] -> [Point]
sortByAngle x = sortBy (comparePoints x)

grahamScanDirections :: [Direction] -> [Bool]
grahamScanDirections [] = []
grahamScanDirections [_] = [True]
grahamScanDirections (ToLeft:ToRight:xs) = False : grahamScanDirections (ToLeft:xs)
grahamScanDirections (ToLeft:ToLeft:xs)  = True  : grahamScanDirections (ToLeft:xs)
grahamScanDirections (ToLeft:Straight:xs) = False : grahamScanDirections (ToLeft:xs)
grahamScanDirections _ = error "Impossible scenario"

filterByList :: [Bool] -> [a] -> [a]
filterByList (True :xs) (y:ys) = y : filterByList xs ys
filterByList (False:xs) (_:ys) = filterByList xs ys
filterByList _ _               = []

grahamScan :: [Point] -> [Point]
grahamScan ps = filterByList bool_list ps1
where ps1       = sortByAngle (lowestYPoint ps) ps
bool_list = grahamScanDirections \$ directions ps1

pss = replicate 1000000
[ Point 1 0
, Point 1 2
, Point 0 3
, Point 2 3
, Point 2 2
, Point 3 2
, Point 2 1
]

instance NFData Point where
rnf a = a seq ()

main = (map grahamScan pss) deepseq putStrLn "Done"