Skip to content

Commit e690d0b

Browse files
committed
test(geometry): Achieve 100% test coverage for ConvexHull
1 parent 7202b49 commit e690d0b

File tree

2 files changed

+68
-37
lines changed

2 files changed

+68
-37
lines changed

src/main/java/com/thealgorithms/geometry/ConvexHull.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,30 @@ private static List<Point> sortCounterClockwise(List<Point> hullPoints) {
168168
return -crossProduct;
169169
});
170170

171-
// Build result with pivot first
171+
// Build result with pivot first, filtering out intermediate collinear points
172172
List<Point> result = new ArrayList<>();
173173
result.add(finalPivot);
174-
result.addAll(sorted);
174+
175+
if (!sorted.isEmpty()) {
176+
// This loop iterates through the points sorted by angle.
177+
// For points that are collinear with the pivot, we only want the one that is farthest away.
178+
// The sort places closer points first.
179+
for (int i = 0; i < sorted.size() - 1; i++) {
180+
// Check the orientation of the pivot, the current point, and the next point.
181+
int orientation = Point.orientation(finalPivot, sorted.get(i), sorted.get(i + 1));
182+
183+
// If the orientation is not 0, it means the next point (i+1) is at a new angle.
184+
// Therefore, the current point (i) must be the farthest point at its angle. We keep it.
185+
if (orientation != 0) {
186+
result.add(sorted.get(i));
187+
}
188+
// If the orientation is 0, the points are collinear. We discard the current point (i)
189+
// because it is closer to the pivot than the next point (i+1).
190+
}
191+
// Always add the very last point from the sorted list. It is either the only point
192+
// at its angle, or it's the farthest among a set of collinear points.
193+
result.add(sorted.getLast());
194+
}
175195

176196
return result;
177197
}

src/test/java/com/thealgorithms/geometry/ConvexHullTest.java

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertTrue;
55

6+
import java.util.ArrayList;
67
import java.util.Arrays;
78
import java.util.List;
89
import org.junit.jupiter.api.Test;
@@ -46,62 +47,35 @@ void testConvexHullRecursive() {
4647
// Test 3: Complex polygon
4748
// Convex hull vertices in CCW order from bottom-most point (2,-4):
4849
// (2,-4) -> (3,0) -> (3,3) -> (0,3) -> (0,0) -> (1,-3) -> back to (2,-4)
49-
points = Arrays.asList(
50-
new Point(0, 3), new Point(2, 2), new Point(1, 1),
51-
new Point(2, 1), new Point(3, 0), new Point(0, 0),
52-
new Point(3, 3), new Point(2, -1), new Point(2, -4),
53-
new Point(1, -3)
54-
);
50+
points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3));
5551
result = ConvexHull.convexHullRecursive(points);
56-
expected = Arrays.asList(
57-
new Point(2, -4), // Bottom-most, left-most (starting point)
58-
new Point(3, 0), // Right side going up
59-
new Point(3, 3), // Top right corner
60-
new Point(0, 3), // Top left corner
61-
new Point(0, 0), // Left side coming down
62-
new Point(1, -3) // Bottom section, back towards start
63-
);
52+
expected = Arrays.asList(new Point(2, -4), new Point(3, 0), new Point(3, 3), new Point(0, 3), new Point(0, 0), new Point(1, -3));
6453
assertEquals(expected, result);
6554
assertTrue(isCounterClockwise(result), "Points should be in counter-clockwise order");
6655
}
6756

6857
@Test
6958
void testConvexHullRecursiveAdditionalCases() {
7059
// Test 4: Square (all corners on hull)
71-
List<Point> points = Arrays.asList(
72-
new Point(0, 0), new Point(2, 0),
73-
new Point(2, 2), new Point(0, 2)
74-
);
60+
List<Point> points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(2, 2), new Point(0, 2));
7561
List<Point> result = ConvexHull.convexHullRecursive(points);
76-
List<Point> expected = Arrays.asList(
77-
new Point(0, 0), new Point(2, 0),
78-
new Point(2, 2), new Point(0, 2)
79-
);
62+
List<Point> expected = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(2, 2), new Point(0, 2));
8063
assertEquals(expected, result);
8164
assertTrue(isCounterClockwise(result), "Square points should be in CCW order");
8265

8366
// Test 5: Pentagon with interior point
84-
points = Arrays.asList(
85-
new Point(0, 0), new Point(4, 0), new Point(5, 3),
86-
new Point(2, 5), new Point(-1, 3), new Point(2, 2) // (2,2) is interior
67+
points = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(5, 3), new Point(2, 5), new Point(-1, 3), new Point(2, 2) // (2,2) is interior
8768
);
8869
result = ConvexHull.convexHullRecursive(points);
8970
// CCW from (0,0): (0,0) -> (4,0) -> (5,3) -> (2,5) -> (-1,3)
90-
expected = Arrays.asList(
91-
new Point(0, 0), new Point(4, 0), new Point(5, 3),
92-
new Point(2, 5), new Point(-1, 3)
93-
);
71+
expected = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(5, 3), new Point(2, 5), new Point(-1, 3));
9472
assertEquals(expected, result);
9573
assertTrue(isCounterClockwise(result), "Pentagon points should be in CCW order");
9674

9775
// Test 6: Simple triangle (clearly convex)
98-
points = Arrays.asList(
99-
new Point(0, 0), new Point(4, 0), new Point(2, 3)
100-
);
76+
points = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3));
10177
result = ConvexHull.convexHullRecursive(points);
102-
expected = Arrays.asList(
103-
new Point(0, 0), new Point(4, 0), new Point(2, 3)
104-
);
78+
expected = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3));
10579
assertEquals(expected, result);
10680
assertTrue(isCounterClockwise(result), "Triangle points should be in CCW order");
10781
}
@@ -124,4 +98,41 @@ private boolean isCounterClockwise(List<Point> points) {
12498

12599
return signedArea > 0; // Positive signed area means counter-clockwise
126100
}
101+
102+
@Test
103+
void testRecursiveHullForCoverage() {
104+
// 1. Test the base cases of the convexHullRecursive method (covering scenarios with < 3 input points).
105+
106+
// Test Case: 0 points
107+
List<Point> pointsEmpty = new ArrayList<>();
108+
List<Point> resultEmpty = ConvexHull.convexHullRecursive(pointsEmpty);
109+
assertTrue(resultEmpty.isEmpty(), "Should return an empty list for an empty input list");
110+
111+
// Test Case: 1 point
112+
List<Point> pointsOne = List.of(new Point(5, 5));
113+
// Pass a new ArrayList because the original method modifies the input list.
114+
List<Point> resultOne = ConvexHull.convexHullRecursive(new ArrayList<>(pointsOne));
115+
List<Point> expectedOne = List.of(new Point(5, 5));
116+
assertEquals(expectedOne, resultOne, "Should return the single point for a single-point input");
117+
118+
// Test Case: 2 points
119+
List<Point> pointsTwo = Arrays.asList(new Point(10, 1), new Point(0, 0));
120+
List<Point> resultTwo = ConvexHull.convexHullRecursive(new ArrayList<>(pointsTwo));
121+
List<Point> expectedTwo = Arrays.asList(new Point(0, 0), new Point(10, 1)); // Should return the two points, sorted.
122+
assertEquals(expectedTwo, resultTwo, "Should return the two sorted points for a two-point input");
123+
124+
// 2. Test the logic for handling collinear points in the sortCounterClockwise method.
125+
126+
// Construct a scenario where multiple collinear points lie on an edge of the convex hull.
127+
// The expected convex hull vertices are (0,0), (10,0), and (5,5).
128+
// When (0,0) is used as the pivot for polar angle sorting, (5,0) and (10,0) are collinear.
129+
// This will trigger the crossProduct == 0 branch in the sortCounterClockwise method.
130+
List<Point> pointsWithCollinearOnHull = Arrays.asList(new Point(0, 0), new Point(5, 0), new Point(10, 0), new Point(5, 5), new Point(2, 2));
131+
132+
List<Point> resultCollinear = ConvexHull.convexHullRecursive(new ArrayList<>(pointsWithCollinearOnHull));
133+
List<Point> expectedCollinear = Arrays.asList(new Point(0, 0), new Point(10, 0), new Point(5, 5));
134+
135+
assertEquals(expectedCollinear, resultCollinear, "Should correctly handle collinear points on the hull edge");
136+
assertTrue(isCounterClockwise(resultCollinear), "The result of the collinear test should be in counter-clockwise order");
137+
}
127138
}

0 commit comments

Comments
 (0)