Real World Haskell 3章 練習問題: GrahamScanアルゴリズムによる凸包の導出

割とHaskellっぽく書けたと思う。

import Data.List

data (Num a) => Point a = Point { x :: a, y :: a } deriving (Eq, Show)

(.+) :: (Num a, Ord a) => Point a -> Point a -> Point a
a .+ b = Point (x a + x b) (y a + y b)

(.-) :: (Num a, Ord a) => Point a -> Point a -> Point a
a .- b = Point (x a - x b) (y a - y b)

data Direction = Clockwise | Counterclockwise | Straight
               deriving (Show)

ccw :: (Num a, Ord a) => Point a -> Point a -> Point a -> Direction
ccw a b c
  | prod == 0 = Straight
  | prod >  0 = Counterclockwise
  | otherwise = Clockwise
    where
      (p, q) = (b.- a, c .- b)
      prod   = (y p) * (x q) - (x p) * (y q)

ccwMap :: (Num a, Ord a) => [Point a] -> [Direction]
ccwMap (x1:x2:x3:xs) = ccw x1 x2 x3 : ccwMap (x2:x3:xs)
ccwMap _             = []

compareCart :: (Num a, Ord a) => Point a -> Point a -> Ordering
compareCart a b =
  if y a /= y b then y a `compare` y b else x a `compare` x b

compareArg :: (Num a, Ord a) => Point a -> Point a -> Point a -> Ordering
compareArg p0 p1 p2 = case ccw p0 p1 p2 of
  Counterclockwise -> GT
  Clockwise        -> LT
  otherwise        -> EQ

grahamScan :: (Num a, Ord a) => [Point a] -> [Point a]
grahamScan pts
  | length pts < 3 = error "grahamScan: list length must be greater than 2"
  | otherwise       = scan (p1:p0:[]) p2s
    where
      p0     = minimumBy compareCart pts
      p1:p2s = sortBy (compareArg p0) (delete p0 pts)
      scan result []          = result
      scan result (next:rest) = case head $ ccwMap (next:result) of
        Clockwise -> scan (tail result) (next:rest)
        otherwise -> scan (next:result) rest

適当に拾ってきたテストデータを食べさせてみる

 *Main> grahamScan $ map (uncurry Point) [(1,1), (1,2), (1,5), (3,5), (4,9), (5,4), (6, 7), (7, 8), (7, 2), (9, 7), (9, 3)]
[Point {x = 1, y = 5},Point {x = 4, y = 9},Point {x = 7, y = 8},Point {x = 9, y = 7},Point {x = 9, y = 3},Point {x = 7, y = 2},Point {x = 1, y = 1}]

出力結果が読みにくいw