Skip to content

Commit d66ae3a

Browse files
committed
feat: add Jarvis-March algorithm
1 parent 9b52ac9 commit d66ae3a

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.thealgorithms.geometry;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Objects;
6+
7+
/**
8+
* This class implements the Jarvis March algorithm (also known as the Gift Wrapping algorithm)
9+
* for computing the convex hull of a set of points in a 2D plane.
10+
* The convex hull is the smallest convex polygon that can enclose all given points.
11+
*/
12+
public final class JarvisMarch {
13+
14+
private JarvisMarch() {
15+
// Private constructor to prevent instantiation
16+
}
17+
18+
/**
19+
* Represents a point in 2D space with x and y coordinates.
20+
*/
21+
static class Point {
22+
private double x;
23+
private double y;
24+
25+
/**
26+
* Constructs a Point with specified x and y coordinates.
27+
*
28+
* @param x the x-coordinate of the point
29+
* @param y the y-coordinate of the point
30+
*/
31+
public Point(double x, double y) {
32+
this.x = x;
33+
this.y = y;
34+
}
35+
36+
public double getX() {
37+
return x;
38+
}
39+
40+
public double getY() {
41+
return y;
42+
}
43+
44+
@Override
45+
public boolean equals(Object obj) {
46+
if (this == obj) return true; // Check if both references point to the same object
47+
if (!(obj instanceof Point)) return false; // Check if obj is an instance of Point
48+
Point other = (Point) obj;
49+
// Compare x and y coordinates for equality
50+
return Double.compare(x, other.x) == 0 && Double.compare(y, other.y) == 0;
51+
}
52+
53+
@Override
54+
public int hashCode() {
55+
return Objects.hash(x, y); // Generate hash code based on x and y coordinates
56+
}
57+
}
58+
59+
/**
60+
* Computes the convex hull of a given list of points using the Jarvis March algorithm.
61+
*
62+
* @param points a list of Points for which to compute the convex hull
63+
* @return a list of Points representing the vertices of the convex hull in counter-clockwise order
64+
*/
65+
public static List<Point> jarvisMarch(List<Point> points) {
66+
List<Point> hull = new ArrayList<>();
67+
68+
// If there are less than 3 points, a convex hull cannot be formed
69+
if (points.size() < 3) return hull;
70+
71+
// Find the leftmost point (with the smallest x-coordinate)
72+
Point leftmost = points.get(0);
73+
for (Point p : points) {
74+
if (p.getX() < leftmost.getX()) {
75+
leftmost = p; // Update leftmost point if a new leftmost point is found
76+
}
77+
}
78+
79+
Point current = leftmost; // Start from the leftmost point
80+
81+
do {
82+
hull.add(current); // Add current point to the hull
83+
84+
Point nextTarget = points.get(0); // Initialize next target as first point in list
85+
86+
for (Point candidate : points) {
87+
if (candidate.equals(current)) continue; // Skip current point
88+
89+
// Check if candidate makes a left turn or is collinear and farther than nextTarget
90+
if ((nextTarget.equals(current) || isLeftTurn(current, nextTarget, candidate)) || (isCollinear(current, nextTarget, candidate) && distance(current, candidate) > distance(current, nextTarget))) {
91+
nextTarget = candidate; // Update next target if conditions are met
92+
}
93+
}
94+
95+
current = nextTarget; // Move to the next target point
96+
97+
} while (!current.equals(leftmost)); // Continue until we loop back to the starting point
98+
99+
return hull; // Return the computed convex hull
100+
}
101+
102+
/**
103+
* Determines whether moving from point A to point B to point C makes a left turn.
104+
*
105+
* @param a the starting point
106+
* @param b the second point
107+
* @param c the third point
108+
* @return true if it makes a left turn, false otherwise
109+
*/
110+
private static boolean isLeftTurn(Point a, Point b, Point c) {
111+
return (b.getX() - a.getX()) * (c.getY() - a.getY()) - (b.getY() - a.getY()) * (c.getX() - a.getX()) > 0;
112+
}
113+
114+
/**
115+
* Checks whether three points A, B, and C are collinear.
116+
*
117+
* @param a the first point
118+
* @param b the second point
119+
* @param c the third point
120+
* @return true if points are collinear, false otherwise
121+
*/
122+
private static boolean isCollinear(Point a, Point b, Point c) {
123+
return (b.getX() - a.getX()) * (c.getY() - a.getY()) == (b.getY() - a.getY()) * (c.getX() - a.getX());
124+
}
125+
126+
/**
127+
* Calculates the Euclidean distance between two points A and B.
128+
*
129+
* @param a the first point
130+
* @param b the second point
131+
* @return the distance between points A and B
132+
*/
133+
private static double distance(Point a, Point b) {
134+
return Math.sqrt(Math.pow(b.getX() - a.getX(), 2) + Math.pow(b.getY() - a.getY(), 2));
135+
}
136+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.thealgorithms.geometry;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
5+
6+
import java.util.Arrays;
7+
import java.util.List;
8+
import java.util.stream.Stream;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.params.ParameterizedTest;
11+
import org.junit.jupiter.params.provider.Arguments;
12+
import org.junit.jupiter.params.provider.MethodSource;
13+
14+
/**
15+
* Unit tests for the {@link JarvisMarch} class, which implements the Jarvis March algorithm
16+
* for computing the convex hull of a set of points.
17+
*/
18+
class JarvisMarchTest {
19+
20+
/**
21+
* Tests the equals method of the Point class with an object of a different type.
22+
* It verifies that a Point instance is not equal to a non-Point object.
23+
*/
24+
@Test
25+
void testEqualsMethodWithDifferentType() {
26+
JarvisMarch.Point pointA = new JarvisMarch.Point(1, 1);
27+
String notAPoint = "I am not a Point";
28+
29+
// Assert that pointA is not equal to a String object
30+
assertNotEquals(pointA, notAPoint);
31+
}
32+
33+
/**
34+
* Provides test cases for the convex hull computation.
35+
* Each case consists of a list of input points and the expected convex hull result.
36+
*
37+
* @return a stream of arguments containing input points and expected hull points
38+
*/
39+
private static Stream<Arguments> providePointsForConvexHull() {
40+
return Stream.of(
41+
// Test case 1: Simple triangle
42+
Arguments.of(Arrays.asList(new JarvisMarch.Point(0, 0), new JarvisMarch.Point(1, 1), new JarvisMarch.Point(1, 0)), Arrays.asList(new JarvisMarch.Point(0, 0), new JarvisMarch.Point(1, 1), new JarvisMarch.Point(1, 0))),
43+
// Test case 2: Points with one point inside the hull
44+
Arguments.of(Arrays.asList(new JarvisMarch.Point(1, 1), new JarvisMarch.Point(0, 0), new JarvisMarch.Point(2, 2), new JarvisMarch.Point(3, 1), new JarvisMarch.Point(2, 0)),
45+
Arrays.asList(new JarvisMarch.Point(0, 0), new JarvisMarch.Point(2, 2), new JarvisMarch.Point(3, 1), new JarvisMarch.Point(2, 0))),
46+
// Test case 3: Single point (no hull)
47+
Arguments.of(Arrays.asList(new JarvisMarch.Point(0, 0)), Arrays.asList()));
48+
}
49+
50+
/**
51+
* Parameterized test for the jarvisMarch method.
52+
* It checks if the actual convex hull computed matches the expected hull
53+
* for various sets of input points.
54+
*
55+
* @param inputPoints a list of points to compute the convex hull from
56+
* @param expectedHull a list of expected points forming the convex hull
57+
*/
58+
@ParameterizedTest
59+
@MethodSource("providePointsForConvexHull")
60+
void testConvexHull(List<JarvisMarch.Point> inputPoints, List<JarvisMarch.Point> expectedHull) {
61+
List<JarvisMarch.Point> actualHull = JarvisMarch.jarvisMarch(inputPoints);
62+
63+
// Assert that the size of actual hull matches the expected hull size
64+
assertEquals(expectedHull.size(), actualHull.size());
65+
66+
// Assert that each point in the expected hull matches the corresponding point in the actual hull
67+
for (int i = 0; i < expectedHull.size(); i++) {
68+
assertEquals(expectedHull.get(i), actualHull.get(i));
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)