Skip to content

Commit 0d17ced

Browse files
authored
Add jarvis march for convex hull (TheAlgorithms#511)
1 parent b5b2eb3 commit 0d17ced

File tree

4 files changed

+207
-1
lines changed

4 files changed

+207
-1
lines changed

src/geometry/graham_scan.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn point_min(a: &&Point, b: &&Point) -> Ordering {
1010
}
1111
}
1212

13-
// Returns a Vec of Points that make up the convex hull of `points`. Returns an empty Vec of there
13+
// Returns a Vec of Points that make up the convex hull of `points`. Returns an empty Vec if there
1414
// is no convex hull.
1515
pub fn graham_scan(mut points: Vec<Point>) -> Vec<Point> {
1616
if points.len() <= 2 {

src/geometry/jarvis_scan.rs

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
use crate::geometry::Point;
2+
use crate::geometry::Segment;
3+
4+
// Returns a Vec of Points that make up the convex hull of `points`. Returns an empty Vec if there
5+
// is no convex hull.
6+
pub fn jarvis_march(points: Vec<Point>) -> Vec<Point> {
7+
if points.len() <= 2 {
8+
return vec![];
9+
}
10+
11+
let mut convex_hull = vec![];
12+
let mut left_point = 0;
13+
for i in 1..points.len() {
14+
// Find the initial point, which is the leftmost point. In the case of a tie, we take the
15+
// bottom-most point. This helps prevent adding colinear points on the last segment to the hull.
16+
if points[i].x < points[left_point].x
17+
|| (points[i].x == points[left_point].x && points[i].y < points[left_point].y)
18+
{
19+
left_point = i;
20+
}
21+
}
22+
convex_hull.push(points[left_point].clone());
23+
24+
let mut p = left_point;
25+
loop {
26+
// Find the next counter-clockwise point.
27+
let mut next_p = (p + 1) % points.len();
28+
for i in 0..points.len() {
29+
let orientation = points[p].consecutive_orientation(&points[i], &points[next_p]);
30+
if orientation > 0.0 {
31+
next_p = i;
32+
}
33+
}
34+
35+
if next_p == left_point {
36+
// Completed constructing the hull. Exit the loop.
37+
break;
38+
}
39+
p = next_p;
40+
41+
let last = convex_hull.len() - 1;
42+
if convex_hull.len() > 1
43+
&& Segment::from_points(points[p].clone(), convex_hull[last - 1].clone())
44+
.on_segment(&convex_hull[last])
45+
{
46+
// If the last point lies on the segment with the new point and the second to last
47+
// point, we can remove the last point from the hull.
48+
convex_hull[last] = points[p].clone();
49+
} else {
50+
convex_hull.push(points[p].clone());
51+
}
52+
}
53+
54+
if convex_hull.len() <= 2 {
55+
return vec![];
56+
}
57+
let last = convex_hull.len() - 1;
58+
if Segment::from_points(convex_hull[0].clone(), convex_hull[last - 1].clone())
59+
.on_segment(&convex_hull[last])
60+
{
61+
// Check for the edge case where the last point lies on the segment with the zero'th and
62+
// second the last point. In this case, we remove the last point from the hull.
63+
convex_hull.pop();
64+
if convex_hull.len() == 2 {
65+
return vec![];
66+
}
67+
}
68+
convex_hull
69+
}
70+
71+
#[cfg(test)]
72+
mod tests {
73+
use super::jarvis_march;
74+
use super::Point;
75+
76+
fn test_jarvis(convex_hull: Vec<Point>, others: Vec<Point>) {
77+
let mut points = others.clone();
78+
points.append(&mut convex_hull.clone());
79+
let jarvis = jarvis_march(points);
80+
for point in convex_hull {
81+
assert!(jarvis.contains(&point));
82+
}
83+
for point in others {
84+
assert!(!jarvis.contains(&point));
85+
}
86+
}
87+
88+
#[test]
89+
fn too_few_points() {
90+
test_jarvis(vec![], vec![]);
91+
test_jarvis(vec![], vec![Point::new(0.0, 0.0)]);
92+
}
93+
94+
#[test]
95+
fn duplicate_point() {
96+
let p = Point::new(0.0, 0.0);
97+
test_jarvis(vec![], vec![p.clone(), p.clone(), p.clone(), p.clone(), p]);
98+
}
99+
100+
#[test]
101+
fn points_same_line() {
102+
let p1 = Point::new(1.0, 0.0);
103+
let p2 = Point::new(2.0, 0.0);
104+
let p3 = Point::new(3.0, 0.0);
105+
let p4 = Point::new(4.0, 0.0);
106+
let p5 = Point::new(5.0, 0.0);
107+
// let p6 = Point::new(1.0, 1.0);
108+
test_jarvis(vec![], vec![p1, p2, p3, p4, p5]);
109+
}
110+
111+
#[test]
112+
fn triangle() {
113+
let p1 = Point::new(1.0, 1.0);
114+
let p2 = Point::new(2.0, 1.0);
115+
let p3 = Point::new(1.5, 2.0);
116+
let points = vec![p1, p2, p3];
117+
test_jarvis(points, vec![]);
118+
}
119+
120+
#[test]
121+
fn rectangle() {
122+
let p1 = Point::new(1.0, 1.0);
123+
let p2 = Point::new(2.0, 1.0);
124+
let p3 = Point::new(2.0, 2.0);
125+
let p4 = Point::new(1.0, 2.0);
126+
let points = vec![p1, p2, p3, p4];
127+
test_jarvis(points, vec![]);
128+
}
129+
130+
#[test]
131+
fn triangle_with_points_in_middle() {
132+
let p1 = Point::new(1.0, 1.0);
133+
let p2 = Point::new(2.0, 1.0);
134+
let p3 = Point::new(1.5, 2.0);
135+
let p4 = Point::new(1.5, 1.5);
136+
let p5 = Point::new(1.2, 1.3);
137+
let p6 = Point::new(1.8, 1.2);
138+
let p7 = Point::new(1.5, 1.9);
139+
let hull = vec![p1, p2, p3];
140+
let others = vec![p4, p5, p6, p7];
141+
test_jarvis(hull, others);
142+
}
143+
144+
#[test]
145+
fn rectangle_with_points_in_middle() {
146+
let p1 = Point::new(1.0, 1.0);
147+
let p2 = Point::new(2.0, 1.0);
148+
let p3 = Point::new(2.0, 2.0);
149+
let p4 = Point::new(1.0, 2.0);
150+
let p5 = Point::new(1.5, 1.5);
151+
let p6 = Point::new(1.2, 1.3);
152+
let p7 = Point::new(1.8, 1.2);
153+
let p8 = Point::new(1.9, 1.7);
154+
let p9 = Point::new(1.4, 1.9);
155+
let hull = vec![p1, p2, p3, p4];
156+
let others = vec![p5, p6, p7, p8, p9];
157+
test_jarvis(hull, others);
158+
}
159+
160+
#[test]
161+
fn star() {
162+
// A single stroke star shape (kind of). Only the tips(p1-5) are part of the convex hull. The
163+
// other points would create angles >180 degrees if they were part of the polygon.
164+
let p1 = Point::new(-5.0, 6.0);
165+
let p2 = Point::new(-11.0, 0.0);
166+
let p3 = Point::new(-9.0, -8.0);
167+
let p4 = Point::new(4.0, 4.0);
168+
let p5 = Point::new(6.0, -7.0);
169+
let p6 = Point::new(-7.0, -2.0);
170+
let p7 = Point::new(-2.0, -4.0);
171+
let p8 = Point::new(0.0, 1.0);
172+
let p9 = Point::new(1.0, 0.0);
173+
let p10 = Point::new(-6.0, 1.0);
174+
let hull = vec![p1, p2, p3, p4, p5];
175+
let others = vec![p6, p7, p8, p9, p10];
176+
test_jarvis(hull, others);
177+
}
178+
179+
#[test]
180+
fn rectangle_with_points_on_same_line() {
181+
let p1 = Point::new(1.0, 1.0);
182+
let p2 = Point::new(2.0, 1.0);
183+
let p3 = Point::new(2.0, 2.0);
184+
let p4 = Point::new(1.0, 2.0);
185+
let p5 = Point::new(1.5, 1.0);
186+
let p6 = Point::new(1.0, 1.5);
187+
let p7 = Point::new(2.0, 1.5);
188+
let p8 = Point::new(1.5, 2.0);
189+
let hull = vec![p1, p2, p3, p4];
190+
let others = vec![p5, p6, p7, p8];
191+
test_jarvis(hull, others);
192+
}
193+
}

src/geometry/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
mod closest_points;
22
mod graham_scan;
3+
mod jarvis_scan;
34
mod point;
45
mod segment;
56

67
pub use self::closest_points::closest_points;
78
pub use graham_scan::graham_scan;
9+
pub use jarvis_scan::jarvis_march;
810
pub use point::Point;
911
pub use segment::Segment;

src/geometry/segment.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ impl Segment {
1515
}
1616
}
1717

18+
pub fn from_points(a: Point, b: Point) -> Segment {
19+
Segment { a, b }
20+
}
21+
1822
pub fn direction(&self, p: &Point) -> f64 {
1923
let a = Point::new(p.x - self.a.x, p.y - self.a.y);
2024
let b = Point::new(self.b.x - self.a.x, self.b.y - self.a.y);
@@ -64,6 +68,13 @@ impl Segment {
6468
p.x >= low_x && p.x <= high_x && p.y >= low_y && p.y <= high_y
6569
}
6670

71+
pub fn on_segment(&self, p: &Point) -> bool {
72+
if !self.is_colinear(p) {
73+
return false;
74+
}
75+
self.colinear_point_on_segment(p)
76+
}
77+
6778
pub fn intersects(&self, other: &Segment) -> bool {
6879
let direction1 = self.direction(&other.a);
6980
let direction2 = self.direction(&other.b);

0 commit comments

Comments
 (0)