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 1 commit
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,31 @@
import Data.List (sortOn, minimumBy)
import Data.Function (on)

type Point = (Double, Double)

angle :: Point -> Point -> Point -> Double
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the same as the old julia function called graham_angle?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, I wrote it without looking at the Julia code. Why?
I know that it's little overdoing it since when it's used in the Graham scan the first two points are always fixed, so I could get away with something more ad-hoc. Although it could make the code slightly harder to read?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at my C code as an example.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example of what? I see you use polar_angle.

Copy link
Contributor

@Gathros Gathros May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this not work?

angle :: Point -> Point -> Double
angle a@(xa, ya) b@(xb, yb)
 | a==b         = 0
 | theta<0      = theta+2*pi
 | otherwise    = theta
 where theta = atan2 (yb-ya) (xb-xa)

and

sortedPts = init $ sortOn (negate . angle p0) pts

I don't know Haskell BTW.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it looks like it would work. I re-used the function that I wrote in Jarvis March for simplicity, but I suppose I could rework this one.

angle a@(xa, ya) b@(xb, yb) c@(xc, yc)
| a==b || c==b = 0
| theta<0 = theta+2*pi
| otherwise = theta
where thetaA = atan2 (ya-yb) (xa-xb)
thetaC = atan2 (yc-yb) (xc-xb)
theta = thetaC - thetaA

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 = init $ sortOn (negate . angle (x, y-1) p0) pts
wrap [] p = p
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
let pts = [(x,y) | x<-[-5..5], y<-[-5..5], x^2+y^2<=5^2]
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:30-32, lang:"julia"](code/julia/graham.jl)
{% sample lang="hs" %}
[import:15-16, lang:"haskell"](code/haskell/grahamScan.hs)
{% endmethod %}

If the output of this function is 0, the points are collinear.
Expand All @@ -24,12 +26,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:34-69, lang:"julia"](code/julia/graham.jl)
{% sample lang="hs" %}
[import:18-27, lang:"haskell"](code/haskell/grahamScan.hs)
{% endmethod %}

### Bibliography
Expand All @@ -40,13 +44,17 @@ 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)
{% endmethod %}

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