patternMinor
Implementation of Graham Scan algorithm in Haskell
Viewed 0 times
grahamscanalgorithmhaskellimplementation
Problem
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 = 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) 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 LT
| xA > xB -> GT
| xA == xB -> case () of
_ | yA 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"
filterByL
```
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 = 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) 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 LT
| xA > xB -> GT
| xA == xB -> case () of
_ | yA 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"
filterByL
Solution
Here are some improvements you may apply.
You can combine pattern matching and records. It allows you to avoid unnecessary numerous calls to
You can force strictness on
These two improvements help with performance.
You can also do the following:
Most of these were given to me by
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
comparePointswith 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 [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 xB = GT
| 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"Code Snippets
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"Context
StackExchange Code Review Q#83125, answer score: 2
Revisions (0)
No revisions yet.