Re: [Haskell-beginners] Imperfect Graham Scan
Typo /counterwise/ - counterclockwise.
BTW, the 'cosine's functionalty can be replaced by the outer product.
2012-1-8 下午11:54 於 "Ray Song" <emacsray@gmail.com> 寫道:
> The 'scan' is flawed. A counterwise angle formed by the first three points
> does not guarantee p1's existence in the hull.
> 2012-1-8 下午3:32 於 "Zhi-Qiang Lei" <zhiqiang.lei@gmail.com> 寫道:
> >
> > Hi,
> >
> > The Graham Scan function I wrote, looks like running well. But when I
> put it in QuickCheck, it just failed in some case. Anyone can show me some
> clues about the problem? Thanks.
> >
> > When I test it in ghci with some example, it returns the right result.
> > *Main> let xs = [Point {x = 1.0, y = 1.0},Point {x = 0.0, y = 4.0},Point
> {x = 0.0, y = 6.0},Point {x = 3.0, y = 5.0},Point {x = 4.0, y = 4.0},Point
> {x = 4.0, y = 1.0},Point {x = 3.0, y = 3.0},Point {x = 2.0, y = 2.0},Point
> {x = 5.0, y = 5.0}]
> > *Main> grahamScan xs
> > [Point {x = 1.0, y = 1.0},Point {x = 4.0, y = 1.0},Point {x = 5.0, y =
> 5.0},Point {x = 0.0, y = 6.0},Point {x = 0.0, y = 4.0}]
> > *Main> grahamScan it
> > [Point {x = 1.0, y = 1.0},Point {x = 4.0, y = 1.0},Point {x = 5.0, y =
> 5.0},Point {x = 0.0, y = 6.0},Point {x = 0.0, y = 4.0}]
> >
> > However, QuickCheck find some points which can fail it. Could it be a
> data type overflow problem?
> >
> > prop_scan_idempotent xs = not (null xs) ==> (grahamScan . grahamScan) xs
> == grahamScan xs
> >
> > *Main> quickCheck prop_scan_idempotent
> > *** Failed! Falsifiable (after 13 tests and 4 shrinks):
> > [Point {x = -6.29996952110807, y = -91.37172300100718},Point {x =
> 9.353314917365527, y = 64.35532141764591},Point {x = -23.826685687218355, y
> = 60.32049750442556},Point {x = -1.4281411275074123, y =
> 31.54197550020998},Point {x = -2.911218918860731, y = 15.564623822256719}]
> >
> > === code ===
> > module GrahamScan (grahamScan, Point(..))
> > where
> >
> > import Data.List
> > import Data.Ratio
> >
> > data Point = Point {
> > x :: Double,
> > y :: Double
> > } deriving (Eq, Show)
> >
> > instance Ord Point where
> > compare (Point x1 y1) (Point x2 y2) = compare (y1, x1) (y2, x2)
> >
> > data Vector = Vector {
> > start :: Point,
> > end :: Point
> > } deriving (Eq)
> >
> > cosine :: Vector -> Double
> > cosine (Vector (Point x1 y1) (Point x2 y2)) = (x2 - x1) / ((x2 - x1) ^ 2
> + (y2 - y1) ^ 2)
> >
> > instance Ord Vector where
> > compare a b = compare (f a) (f b) where
> > f = negate . cosine
> >
> > sort' :: [Point] -> [Point]
> > sort' xs = pivot : fmap end sortedVectors where
> > sortedVectors = sort . fmap (Vector pivot) . delete pivot $ xs
> > pivot = minimum xs
> >
> > counterClockwise :: Point -> Point -> Point -> Bool
> > counterClockwise (Point x1 y1) (Point x2 y2) (Point x3 y3) = (x2 - x1) *
> (y3 - y1) > (y2 - y1) * (x3 - x1)
> >
> > scan :: [Point] -> [Point]
> > scan (p1 : p2 : p3 : xs)
> > | counterClockwise p1 p2 p3 = p1 : scan (p2 : p3 : xs)
> > | otherwise = scan (p1 : p3 : xs)
> > scan xs = xs
> >
> > grahamScan :: [Point] -> [Point]
> > grahamScan = scan . sort' . nub
> > === code ===
> >
> >
> > Best regards,
> > Zhi-Qiang Lei
> > zhiqiang.lei@gmail.com
> >
> >
> > _______________________________________________
> > Beginners mailing list
> > Beginners@haskell.org
> > http://www.haskell.org/mailman/listinfo/beginners
>
I think I find what the problem is: When calculating the distance in cosine function, a sqrt is missing. There is no pivot append to the sorted list of points in sort'. The algorithm which scan implement is incorrect. Read more details in my comments. I appreciate you all. === prop_scan_idempotent on GrahamScan_qc.hs:8 === +++ OK, passed 100 tests. === Code === module GrahamScan (grahamScan, Point(..)) where import Data.List import Data.Ratio data Point = Point { x :: Double, y :: Double } deriving (Eq, Show) instance Ord Point where compare (Point x1 y1) (Point x2 y2) = compare (y1, x1) (y2, x2) data Vector = Vector { start :: Point, end :: Point } deriving (Eq) cosine :: Vector -> Double cosine (Vector (Point x1 y1) (Point x2 y2)) = (x2 - x1) / distance where distance = sqrt $ (x2 - x1) ^ 2 + (y2 - y1) ^ 2 instance Ord Vector where compare a b = compare (f a) (f b) where f = negate . cosine -- After sorting a pivot should be append to the sorted list impermanently. -- Otherwise the last point could not be examine. sort' :: [Point] -> [Point] sort' xs = pivot : fmap end sortedVectors ++ [pivot] where sortedVectors = sort . fmap (Vector pivot) . delete pivot $ xs pivot = minimum xs isCounterClockwise :: Point -> Point -> Point -> Bool isCounterClockwise (Point x1 y1) (Point x2 y2) (Point x3 y3) = (x2 - x1) * (y3 - y1) > (y2 - y1) * (x3 - x1) -- When a point is considered clockwise or collinear, just removing it -- is not enough, the point before it has to be re-examined. Or else, -- the function is not idempotent. This is not mentioned on Wikipedia. scan' :: ([Point], [Point]) -> ([Point], [Point]) scan' (p1 : p2 : p3 : xs, ys) | isCounterClockwise p1 p2 p3 = scan' (p2 : p3 : xs, ys ++ [p1]) | otherwise = scan' (last ys : p1 : p3 : xs, init ys) scan' (xs, ys) = ([], ys ++ xs) -- The last point is pivot, ignore it in result. scan :: [Point] -> [Point] scan xs = init . (\(_, ys) -> ys) . scan' $ (xs, []) grahamScan :: [Point] -> [Point] grahamScan xs@(_ : _ : _ : _) = scan . sort' . nub $ xs === Code === Best regards, Zhi-Qiang Lei zhiqiang.lei@gmail.com
participants (2)
-
Ray Song -
Zhi-Qiang Lei