HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Implementation of Graham Scan algorithm in Haskell

Submitted by: @import:stackexchange-codereview··
0
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

Solution

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  [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.