Skip to content

Added Graham Scan in Haskell #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Data.List (sortOn, minimumBy)
import Data.Function (on)

type Point = (Double, Double)

ccw :: Point -> Point -> Point -> Double
ccw (xa, ya) (xb, yb) (xc, yc) = (xb - xa) * (yc - ya) - (yb - ya) * (xc - xa)

grahamScan :: [Point] -> [Point]
grahamScan [] = []
grahamScan pts = wrap sortedPts [p0]
where p0@(x, y)= minimumBy (compare `on` snd) pts
sortedPts = sortOn (\(px, py) -> atan2 (py-y) (px-x) ) $ filter (/=p0) pts
wrap [] ps = ps
wrap (s:ss) [p] = wrap ss [s, p]
wrap (s:ss) (p1:p2:ps)
| ccw s p1 p2 > 0 = wrap (s:ss) (p2:ps)
| otherwise = wrap ss (s:p1:p2:ps)

main = do
-- We build the set of points of integer coordinates within a circle of radius 5
let pts = [(x,y) | x<-[-5..5], y<-[-5..5], x^2+y^2<=5^2]
-- And extract the convex hull
print $ grahamScan pts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Graham Scan

At around the same time of the [Jarvis March](jarvis_march.md), R. L. Graham was also developing an algorithm to find the convex hull of a random set of points {{ "gs1972" | cite }}.
Unlike the Jarvis March, which is an $$\mathcal{O}(nh)$$ operation, the Graham Scan is $$\mathcal{O}(n\log(n))$$, where $$n$$ is the number of points and $$h$$ is the size fo the hull.
Unlike the Jarvis March, which is an $$\mathcal{O}(nh)$$ operation, the Graham Scan is $$\mathcal{O}(n\log(n))$$, where $$n$$ is the number of points and $$h$$ is the size fo the hull.
This means that the complexity of the Graham Scan is not output-sensitive; moreover, there are some cases where the Jarvis March is more optimal, depending on the size of the hull and the number of points to wrap.

Rather than starting at the leftmost point like the Jarvis March, the Graham scan starts at the bottom.
Rather than starting at the leftmost point like the Jarvis March, the Graham scan starts at the bottom.
We then sort the distribution of points based on the angle between the bottom-most point, the origin, and each other point.
After sorting, we go through point-by-point, searching for points that are on the convex hull and throwing out any other points.
We do this by looking for counter-clockwise rotations.
Expand All @@ -14,6 +14,8 @@ We can find whether a rotation is counter-clockwise with trigonometric functions
{% method %}
{% sample lang="jl" %}
[import:6-8, lang:"julia"](code/julia/graham.jl)
{% sample lang="hs" %}
[import:6-7, lang:"haskell"](code/haskell/grahamScan.hs)
{% sample lang="c" %}
[import:24-26, lang:"c_cpp"](code/c/graham.c)
{% endmethod %}
Expand All @@ -26,12 +28,14 @@ We basically do not want clockwise rotations, because this means we are at an in
<!---ADD FIGURE--->

To save memory and expensive `append()` operations, we ultimately look for points that should be on the hull and swap them with the first elements in the array.
If there are $$M$$ elements on the hull, then the first $$M$$ elements in our output random distribution of points will be the hull.
If there are $$M$$ elements on the hull, then the first $$M$$ elements in our output random distribution of points will be the hull.
In the end, the code should look something like this:

{% method %}
{% sample lang="jl" %}
[import:10-46, lang:"julia"](code/julia/graham.jl)
{% sample lang="hs" %}
[import:9-18, lang:"haskell"](code/haskell/grahamScan.hs)
{% sample lang="c" %}
[import:65-95, lang:"c_cpp"](code/c/graham.c)
{% endmethod %}
Expand All @@ -44,15 +48,20 @@ In the end, the code should look something like this:

{% method %}
{% sample lang="jl" %}
### Julia
[import, lang:"julia"](code/julia/graham.jl)
{% sample lang="hs" %}
### Haskell
[import, lang:"haskell"](code/haskell/grahamScan.hs)
{% sample lang="c" %}
### C
[import, lang:"c_cpp"](code/c/graham.c)
{% endmethod %}

<script>
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
</script>
$$
$$
\newcommand{\d}{\mathrm{d}}
\newcommand{\bff}{\boldsymbol{f}}
\newcommand{\bfg}{\boldsymbol{g}}
Expand All @@ -71,4 +80,3 @@ $$
\newcommand{\bfomega}{\boldsymbol{\omega}}
\newcommand{\bftau}{\boldsymbol{\tau}}
$$