From 830e499f17b243a4c42a904d3e6dce734f705adb Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Tue, 7 Oct 2025 20:01:32 +0530 Subject: [PATCH 1/8] Added RotatingCalipers to geometry --- .../geometry/RotatingCalipers.java | 321 ++++++++++++++++ .../geometry/RotatingCalipersTest.java | 345 ++++++++++++++++++ 2 files changed, 666 insertions(+) create mode 100644 src/main/java/com/thealgorithms/geometry/RotatingCalipers.java create mode 100644 src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java new file mode 100644 index 000000000000..42985b356cbd --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -0,0 +1,321 @@ +package com.thealgorithms.geometry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class implementing the Rotating Calipers algorithm for geometric computations on convex polygons. + * + * The Rotating Calipers algorithm is an efficient technique for solving various geometric problems + * on convex polygons, including: + * - Computing the diameter (maximum distance between any two points) + * - Computing the width (minimum distance between parallel supporting lines) + * - Finding the minimum-area bounding rectangle + * + * Algorithm Description: + * 1. Compute the convex hull of the given points + * 2. Use rotating calipers (parallel lines) that rotate around the convex hull + * 3. For each rotation, compute the desired geometric property + * 4. Return the optimal result + * + * Time Complexity: O(n) where n is the number of points in the convex hull + * Space Complexity: O(n) for storing the convex hull + * + * Reference: + * Shamos, M. I. (1978). Computational Geometry. + * + * @author TheAlgorithms + */ +public final class RotatingCalipers { + + private RotatingCalipers() { + } + + /** + * Represents a pair of points with their distance. + */ + public static record PointPair(Point p1, Point p2, double distance) { + @Override + public String toString() { + return String.format("PointPair(%s, %s, distance=%.2f)", p1, p2, distance); + } + } + + /** + * Represents a rectangle with its area. + */ + public static record Rectangle(Point bottomLeft, Point topRight, double area) { + @Override + public String toString() { + return String.format("Rectangle(%s, %s, area=%.2f)", bottomLeft, topRight, area); + } + } + + /** + * Computes the diameter of a convex polygon using rotating calipers. + * The diameter is the maximum distance between any two points of the polygon. + * + * @param points List of points representing a convex polygon + * @return PointPair containing the two points with maximum distance and the distance + * @throws IllegalArgumentException if points is null or has less than 2 points + */ + public static PointPair computeDiameter(List points) { + if (points == null || points.size() < 2) { + throw new IllegalArgumentException("Points list must contain at least 2 points"); + } + + List hull = ConvexHull.convexHullRecursive(new ArrayList<>(points)); + if (hull.size() < 2) { + throw new IllegalArgumentException("Convex hull must contain at least 2 points"); + } + + hull = ensureCounterClockwiseOrder(hull); + + if (hull.size() == 2) { + Point p1 = hull.get(0); + Point p2 = hull.get(1); + return new PointPair(p1, p2, distance(p1, p2)); + } + + int n = hull.size(); + PointPair maxPair = null; + double maxDistance = 0.0; + + int j = 1; + for (int i = 0; i < n; i++) { + Point p1 = hull.get(i); + + while (true) { + Point next = hull.get((j + 1) % n); + double dist1 = distance(p1, hull.get(j)); + double dist2 = distance(p1, next); + + if (dist2 > dist1) { + j = (j + 1) % n; + } else { + break; + } + } + + double dist = distance(p1, hull.get(j)); + if (dist > maxDistance) { + maxDistance = dist; + maxPair = new PointPair(p1, hull.get(j), dist); + } + } + + return maxPair; + } + + /** + * Computes the width of a convex polygon using rotating calipers. + * The width is the minimum distance between two parallel supporting lines. + * + * @param points List of points representing a convex polygon + * @return The minimum width of the polygon + * @throws IllegalArgumentException if points is null or has less than 2 points + */ + public static double computeWidth(List points) { + if (points == null || points.size() < 2) { + throw new IllegalArgumentException("Points list must contain at least 2 points"); + } + + List hull = ConvexHull.convexHullRecursive(new ArrayList<>(points)); + if (hull.size() < 2) { + throw new IllegalArgumentException("Convex hull must contain at least 2 points"); + } + + hull = ensureCounterClockwiseOrder(hull); + + if (hull.size() == 2) { + return 0.0; + } + + int n = hull.size(); + double minWidth = Double.MAX_VALUE; + + int j = 1; + for (int i = 0; i < n; i++) { + Point p1 = hull.get(i); + Point p2 = hull.get((i + 1) % n); + + while (true) { + Point next = hull.get((j + 1) % n); + double dist1 = distanceToLine(p1, p2, hull.get(j)); + double dist2 = distanceToLine(p1, p2, next); + + if (dist2 > dist1) { + j = (j + 1) % n; + } else { + break; + } + } + + double width = distanceToLine(p1, p2, hull.get(j)); + minWidth = Math.min(minWidth, width); + } + + return minWidth; + } + + /** + * Computes the minimum-area bounding rectangle of a convex polygon using rotating calipers. + * + * @param points List of points representing a convex polygon + * @return Rectangle containing the minimum-area bounding rectangle + * @throws IllegalArgumentException if points is null or has less than 2 points + */ + public static Rectangle computeMinimumAreaBoundingRectangle(List points) { + if (points == null || points.size() < 2) { + throw new IllegalArgumentException("Points list must contain at least 2 points"); + } + + List hull = ConvexHull.convexHullRecursive(new ArrayList<>(points)); + if (hull.size() < 2) { + throw new IllegalArgumentException("Convex hull must contain at least 2 points"); + } + + hull = ensureCounterClockwiseOrder(hull); + + if (hull.size() == 2) { + Point p1 = hull.get(0); + Point p2 = hull.get(1); + return new Rectangle(p1, p2, 0.0); + } + + int n = hull.size(); + double minArea = Double.MAX_VALUE; + Rectangle bestRectangle = null; + + for (int i = 0; i < n; i++) { + Point p1 = hull.get(i); + Point p2 = hull.get((i + 1) % n); + + int j = findAntipodalPoint(hull, i); + + double edgeLength = distance(p1, p2); + double height = distanceToLine(p1, p2, hull.get(j)); + + double area = edgeLength * height; + + if (area < minArea) { + minArea = area; + Point bottomLeft = computeRectangleCorner(p1, p2, hull.get(j), true); + Point topRight = computeRectangleCorner(p1, p2, hull.get(j), false); + bestRectangle = new Rectangle(bottomLeft, topRight, area); + } + } + + return bestRectangle; + } + + /** + * Finds the antipodal point for a given edge using rotating calipers. + */ + private static int findAntipodalPoint(List hull, int edgeStart) { + int n = hull.size(); + int j = (edgeStart + 1) % n; + + Point p1 = hull.get(edgeStart); + Point p2 = hull.get((edgeStart + 1) % n); + + while (true) { + Point next = hull.get((j + 1) % n); + double dist1 = distanceToLine(p1, p2, hull.get(j)); + double dist2 = distanceToLine(p1, p2, next); + + if (dist2 > dist1) { + j = (j + 1) % n; + } else { + break; + } + } + + return j; + } + + /** + * Computes a corner of the bounding rectangle. + */ + private static Point computeRectangleCorner(Point p1, Point p2, Point antipodal, boolean isBottomLeft) { + int minX = Math.min(Math.min(p1.x(), p2.x()), antipodal.x()); + int maxX = Math.max(Math.max(p1.x(), p2.x()), antipodal.x()); + int minY = Math.min(Math.min(p1.y(), p2.y()), antipodal.y()); + int maxY = Math.max(Math.max(p1.y(), p2.y()), antipodal.y()); + + if (isBottomLeft) { + return new Point(minX, minY); + } else { + return new Point(maxX, maxY); + } + } + + /** + * Computes the Euclidean distance between two points. + */ + private static double distance(Point p1, Point p2) { + int dx = p2.x() - p1.x(); + int dy = p2.y() - p1.y(); + return Math.sqrt(dx * dx + dy * dy); + } + + /** + * Computes the perpendicular distance from a point to a line defined by two points. + */ + private static double distanceToLine(Point lineStart, Point lineEnd, Point point) { + int dx = lineEnd.x() - lineStart.x(); + int dy = lineEnd.y() - lineStart.y(); + + if (dx == 0 && dy == 0) { + return distance(lineStart, point); + } + + int px = point.x() - lineStart.x(); + int py = point.y() - lineStart.y(); + + double crossProduct = Math.abs(px * dy - py * dx); + double lineLength = Math.sqrt(dx * dx + dy * dy); + + return crossProduct / lineLength; + } + + /** + * Ensures the hull points are in counter-clockwise order for rotating calipers. + * The convex hull algorithm returns points sorted by natural order, but rotating calipers + * requires counter-clockwise ordering. + */ + private static List ensureCounterClockwiseOrder(List hull) { + if (hull.size() <= 2) { + return hull; + } + + Point bottomMost = hull.get(0); + int bottomIndex = 0; + for (int i = 1; i < hull.size(); i++) { + Point p = hull.get(i); + if (p.y() < bottomMost.y() || (p.y() == bottomMost.y() && p.x() < bottomMost.x())) { + bottomMost = p; + bottomIndex = i; + } + } + + List orderedHull = new ArrayList<>(); + for (int i = 0; i < hull.size(); i++) { + orderedHull.add(hull.get((bottomIndex + i) % hull.size())); + } + + if (orderedHull.size() >= 3) { + Point p1 = orderedHull.get(0); + Point p2 = orderedHull.get(1); + Point p3 = orderedHull.get(2); + + if (Point.orientation(p1, p2, p3) < 0) { + Collections.reverse(orderedHull); + Collections.rotate(orderedHull, 1); + } + } + + return orderedHull; + } +} diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java new file mode 100644 index 000000000000..0701e9589475 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -0,0 +1,345 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the RotatingCalipers class. + * Tests cover various scenarios including simple cases, edge cases, and complex polygons. + */ +public class RotatingCalipersTest { + + @Test + void testComputeDiameterWithTwoPoints() { + List points = Arrays.asList(new Point(0, 0), new Point(3, 4)); + RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); + + assertNotNull(result); + assertEquals(5.0, result.distance(), 1e-9); + assertTrue((result.p1().equals(new Point(0, 0)) && result.p2().equals(new Point(3, 4))) || + (result.p1().equals(new Point(3, 4)) && result.p2().equals(new Point(0, 0)))); + } + + @Test + void testComputeDiameterWithTriangle() { + List points = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(1, 2) + ); + RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); + + assertNotNull(result); + assertEquals(3.0, result.distance(), 1e-9); + assertTrue((result.p1().equals(new Point(0, 0)) && result.p2().equals(new Point(3, 0))) || + (result.p1().equals(new Point(3, 0)) && result.p2().equals(new Point(0, 0)))); + } + + @Test + void testComputeDiameterWithSquare() { + List points = Arrays.asList( + new Point(0, 0), + new Point(2, 0), + new Point(2, 2), + new Point(0, 2) + ); + RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); + + assertNotNull(result); + assertEquals(Math.sqrt(8), result.distance(), 1e-9); // Diagonal of square + } + + @Test + void testComputeDiameterWithRectangle() { + List points = Arrays.asList( + new Point(0, 0), + new Point(4, 0), + new Point(4, 2), + new Point(0, 2) + ); + RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); + + assertNotNull(result); + assertEquals(Math.sqrt(20), result.distance(), 1e-9); // Diagonal of rectangle + } + + @Test + void testComputeDiameterWithPentagon() { + List points = Arrays.asList( + new Point(0, 0), + new Point(2, 0), + new Point(3, 1), + new Point(1, 2), + new Point(-1, 1) + ); + RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); + + assertNotNull(result); + assertTrue(result.distance() > 0); + } + + @Test + void testComputeDiameterWithNullPoints() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeDiameter(null); + }); + } + + @Test + void testComputeDiameterWithEmptyPoints() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeDiameter(new ArrayList<>()); + }); + } + + @Test + void testComputeDiameterWithSinglePoint() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeDiameter(Arrays.asList(new Point(0, 0))); + }); + } + + @Test + void testComputeWidthWithTwoPoints() { + List points = Arrays.asList(new Point(0, 0), new Point(3, 4)); + double width = RotatingCalipers.computeWidth(points); + + assertEquals(0.0, width, 1e-9); + } + + @Test + void testComputeWidthWithTriangle() { + List points = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(1, 2) + ); + double width = RotatingCalipers.computeWidth(points); + + assertTrue(width > 0); + assertTrue(width < 3.0); // Should be less than the base width + } + + @Test + void testComputeWidthWithSquare() { + List points = Arrays.asList( + new Point(0, 0), + new Point(2, 0), + new Point(2, 2), + new Point(0, 2) + ); + double width = RotatingCalipers.computeWidth(points); + + assertEquals(Math.sqrt(2), width, 1e-9); // Width of square + } + + @Test + void testComputeWidthWithRectangle() { + List points = Arrays.asList( + new Point(0, 0), + new Point(4, 0), + new Point(4, 2), + new Point(0, 2) + ); + double width = RotatingCalipers.computeWidth(points); + + assertEquals(Math.sqrt(5), width, 1e-9); // Width of rectangle + } + + @Test + void testComputeWidthWithNullPoints() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeWidth(null); + }); + } + + @Test + void testComputeWidthWithEmptyPoints() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeWidth(new ArrayList<>()); + }); + } + + @Test + void testComputeWidthWithSinglePoint() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeWidth(Arrays.asList(new Point(0, 0))); + }); + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithTwoPoints() { + List points = Arrays.asList(new Point(0, 0), new Point(3, 4)); + RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); + + assertNotNull(result); + assertEquals(0.0, result.area(), 1e-9); + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithTriangle() { + List points = Arrays.asList( + new Point(0, 0), + new Point(3, 0), + new Point(1, 2) + ); + RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); + + assertNotNull(result); + assertTrue(result.area() > 0); + assertTrue(result.area() <= 6.0); // Should be less than or equal to axis-aligned bounding box + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithSquare() { + List points = Arrays.asList( + new Point(0, 0), + new Point(2, 0), + new Point(2, 2), + new Point(0, 2) + ); + RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); + + assertNotNull(result); + assertEquals(4.0, result.area(), 1e-9); // Area of square + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithRectangle() { + List points = Arrays.asList( + new Point(0, 0), + new Point(4, 0), + new Point(4, 2), + new Point(0, 2) + ); + RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); + + assertNotNull(result); + assertEquals(8.0, result.area(), 1e-9); // Area of rectangle + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithPentagon() { + List points = Arrays.asList( + new Point(0, 0), + new Point(2, 0), + new Point(3, 1), + new Point(1, 2), + new Point(-1, 1) + ); + RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); + + assertNotNull(result); + assertTrue(result.area() > 0); + assertTrue(result.area() <= 8.0); // Should be less than or equal to axis-aligned bounding box + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithNullPoints() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeMinimumAreaBoundingRectangle(null); + }); + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithEmptyPoints() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeMinimumAreaBoundingRectangle(new ArrayList<>()); + }); + } + + @Test + void testComputeMinimumAreaBoundingRectangleWithSinglePoint() { + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeMinimumAreaBoundingRectangle(Arrays.asList(new Point(0, 0))); + }); + } + + @Test + void testPointPairToString() { + Point p1 = new Point(0, 0); + Point p2 = new Point(3, 4); + RotatingCalipers.PointPair pair = new RotatingCalipers.PointPair(p1, p2, 5.0); + + String str = pair.toString(); + assertTrue(str.contains("PointPair")); + assertTrue(str.contains("distance=5.00")); + } + + @Test + void testRectangleToString() { + Point bottomLeft = new Point(0, 0); + Point topRight = new Point(2, 2); + RotatingCalipers.Rectangle rect = new RotatingCalipers.Rectangle(bottomLeft, topRight, 4.0); + + String str = rect.toString(); + assertTrue(str.contains("Rectangle")); + assertTrue(str.contains("area=4.00")); + } + + @Test + void testComplexPolygon() { + // Test with a more complex polygon (hexagon) + List points = Arrays.asList( + new Point(0, 0), + new Point(2, 0), + new Point(3, 1), + new Point(2, 2), + new Point(0, 2), + new Point(-1, 1) + ); + + RotatingCalipers.PointPair diameter = RotatingCalipers.computeDiameter(points); + double width = RotatingCalipers.computeWidth(points); + RotatingCalipers.Rectangle rectangle = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); + + assertNotNull(diameter); + assertNotNull(rectangle); + assertTrue(diameter.distance() > 0); + assertTrue(width > 0); + assertTrue(rectangle.area() > 0); + } + + @Test + void testCollinearPoints() { + // Test with collinear points + List points = Arrays.asList( + new Point(0, 0), + new Point(1, 0), + new Point(2, 0), + new Point(3, 0) + ); + + RotatingCalipers.PointPair diameter = RotatingCalipers.computeDiameter(points); + double width = RotatingCalipers.computeWidth(points); + RotatingCalipers.Rectangle rectangle = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); + + assertNotNull(diameter); + assertNotNull(rectangle); + assertEquals(3.0, diameter.distance(), 1e-9); + assertEquals(0.0, width, 1e-9); + assertEquals(0.0, rectangle.area(), 1e-9); + } + + @Test + void testSinglePointConvexHull() { + // Test edge case where convex hull reduces to a single point + List points = Arrays.asList( + new Point(0, 0), + new Point(0, 0), + new Point(0, 0) + ); + + assertThrows(IllegalArgumentException.class, () -> { + RotatingCalipers.computeDiameter(points); + }); + } +} From 2f46d1f994fc5e0eda1db494910db6f5b3c75332 Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Tue, 7 Oct 2025 20:42:39 +0530 Subject: [PATCH 2/8] improvement in the code - (added the RotatingCalipers to geometry) --- .../thealgorithms/geometry/RotatingCalipers.java | 16 ++++------------ .../geometry/RotatingCalipersTest.java | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 42985b356cbd..d431289a05b0 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -135,23 +135,15 @@ public static double computeWidth(List points) { int n = hull.size(); double minWidth = Double.MAX_VALUE; - int j = 1; + // Use rotating calipers to find minimum width for (int i = 0; i < n; i++) { Point p1 = hull.get(i); Point p2 = hull.get((i + 1) % n); - while (true) { - Point next = hull.get((j + 1) % n); - double dist1 = distanceToLine(p1, p2, hull.get(j)); - double dist2 = distanceToLine(p1, p2, next); - - if (dist2 > dist1) { - j = (j + 1) % n; - } else { - break; - } - } + // Find the antipodal point for this edge + int j = findAntipodalPoint(hull, i); + // Compute width as distance between parallel lines double width = distanceToLine(p1, p2, hull.get(j)); minWidth = Math.min(minWidth, width); } diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java index 0701e9589475..2bb470ce6fa8 100644 --- a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -151,7 +151,7 @@ void testComputeWidthWithRectangle() { ); double width = RotatingCalipers.computeWidth(points); - assertEquals(Math.sqrt(5), width, 1e-9); // Width of rectangle + assertEquals(2.0, width, 1e-9); // Width of rectangle (height) } @Test From 44c8a26e19c2831ae4941193cc8948562ce941ce Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Tue, 7 Oct 2025 21:14:02 +0530 Subject: [PATCH 3/8] improvements are made --- .../geometry/RotatingCalipers.java | 120 +++++------ .../geometry/RotatingCalipersTest.java | 190 +++++------------- 2 files changed, 108 insertions(+), 202 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index d431289a05b0..9579052b7fd8 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -6,32 +6,32 @@ /** * A class implementing the Rotating Calipers algorithm for geometric computations on convex polygons. - * + * * The Rotating Calipers algorithm is an efficient technique for solving various geometric problems * on convex polygons, including: * - Computing the diameter (maximum distance between any two points) * - Computing the width (minimum distance between parallel supporting lines) * - Finding the minimum-area bounding rectangle - * + * * Algorithm Description: * 1. Compute the convex hull of the given points * 2. Use rotating calipers (parallel lines) that rotate around the convex hull * 3. For each rotation, compute the desired geometric property * 4. Return the optimal result - * + * * Time Complexity: O(n) where n is the number of points in the convex hull * Space Complexity: O(n) for storing the convex hull - * + * * Reference: * Shamos, M. I. (1978). Computational Geometry. - * + * * @author TheAlgorithms */ public final class RotatingCalipers { - + private RotatingCalipers() { } - + /** * Represents a pair of points with their distance. */ @@ -41,7 +41,7 @@ public String toString() { return String.format("PointPair(%s, %s, distance=%.2f)", p1, p2, distance); } } - + /** * Represents a rectangle with its area. */ @@ -51,11 +51,11 @@ public String toString() { return String.format("Rectangle(%s, %s, area=%.2f)", bottomLeft, topRight, area); } } - + /** * Computes the diameter of a convex polygon using rotating calipers. * The diameter is the maximum distance between any two points of the polygon. - * + * * @param points List of points representing a convex polygon * @return PointPair containing the two points with maximum distance and the distance * @throws IllegalArgumentException if points is null or has less than 2 points @@ -64,54 +64,54 @@ public static PointPair computeDiameter(List points) { if (points == null || points.size() < 2) { throw new IllegalArgumentException("Points list must contain at least 2 points"); } - + List hull = ConvexHull.convexHullRecursive(new ArrayList<>(points)); if (hull.size() < 2) { throw new IllegalArgumentException("Convex hull must contain at least 2 points"); } - + hull = ensureCounterClockwiseOrder(hull); - + if (hull.size() == 2) { Point p1 = hull.get(0); Point p2 = hull.get(1); return new PointPair(p1, p2, distance(p1, p2)); } - + int n = hull.size(); PointPair maxPair = null; double maxDistance = 0.0; - + int j = 1; for (int i = 0; i < n; i++) { Point p1 = hull.get(i); - + while (true) { Point next = hull.get((j + 1) % n); double dist1 = distance(p1, hull.get(j)); double dist2 = distance(p1, next); - + if (dist2 > dist1) { j = (j + 1) % n; } else { break; } } - + double dist = distance(p1, hull.get(j)); if (dist > maxDistance) { maxDistance = dist; maxPair = new PointPair(p1, hull.get(j), dist); } } - + return maxPair; } - + /** * Computes the width of a convex polygon using rotating calipers. * The width is the minimum distance between two parallel supporting lines. - * + * * @param points List of points representing a convex polygon * @return The minimum width of the polygon * @throws IllegalArgumentException if points is null or has less than 2 points @@ -120,40 +120,40 @@ public static double computeWidth(List points) { if (points == null || points.size() < 2) { throw new IllegalArgumentException("Points list must contain at least 2 points"); } - + List hull = ConvexHull.convexHullRecursive(new ArrayList<>(points)); if (hull.size() < 2) { throw new IllegalArgumentException("Convex hull must contain at least 2 points"); } - + hull = ensureCounterClockwiseOrder(hull); - + if (hull.size() == 2) { return 0.0; } - + int n = hull.size(); double minWidth = Double.MAX_VALUE; - + // Use rotating calipers to find minimum width for (int i = 0; i < n; i++) { Point p1 = hull.get(i); Point p2 = hull.get((i + 1) % n); - + // Find the antipodal point for this edge int j = findAntipodalPoint(hull, i); - + // Compute width as distance between parallel lines double width = distanceToLine(p1, p2, hull.get(j)); minWidth = Math.min(minWidth, width); } - + return minWidth; } - + /** * Computes the minimum-area bounding rectangle of a convex polygon using rotating calipers. - * + * * @param points List of points representing a convex polygon * @return Rectangle containing the minimum-area bounding rectangle * @throws IllegalArgumentException if points is null or has less than 2 points @@ -162,35 +162,35 @@ public static Rectangle computeMinimumAreaBoundingRectangle(List points) if (points == null || points.size() < 2) { throw new IllegalArgumentException("Points list must contain at least 2 points"); } - + List hull = ConvexHull.convexHullRecursive(new ArrayList<>(points)); if (hull.size() < 2) { throw new IllegalArgumentException("Convex hull must contain at least 2 points"); } - + hull = ensureCounterClockwiseOrder(hull); - + if (hull.size() == 2) { Point p1 = hull.get(0); Point p2 = hull.get(1); return new Rectangle(p1, p2, 0.0); } - + int n = hull.size(); double minArea = Double.MAX_VALUE; Rectangle bestRectangle = null; - + for (int i = 0; i < n; i++) { Point p1 = hull.get(i); Point p2 = hull.get((i + 1) % n); - + int j = findAntipodalPoint(hull, i); - + double edgeLength = distance(p1, p2); double height = distanceToLine(p1, p2, hull.get(j)); - + double area = edgeLength * height; - + if (area < minArea) { minArea = area; Point bottomLeft = computeRectangleCorner(p1, p2, hull.get(j), true); @@ -198,35 +198,35 @@ public static Rectangle computeMinimumAreaBoundingRectangle(List points) bestRectangle = new Rectangle(bottomLeft, topRight, area); } } - + return bestRectangle; } - + /** * Finds the antipodal point for a given edge using rotating calipers. */ private static int findAntipodalPoint(List hull, int edgeStart) { int n = hull.size(); int j = (edgeStart + 1) % n; - + Point p1 = hull.get(edgeStart); Point p2 = hull.get((edgeStart + 1) % n); - + while (true) { Point next = hull.get((j + 1) % n); double dist1 = distanceToLine(p1, p2, hull.get(j)); double dist2 = distanceToLine(p1, p2, next); - + if (dist2 > dist1) { j = (j + 1) % n; } else { break; } } - + return j; } - + /** * Computes a corner of the bounding rectangle. */ @@ -235,14 +235,14 @@ private static Point computeRectangleCorner(Point p1, Point p2, Point antipodal, int maxX = Math.max(Math.max(p1.x(), p2.x()), antipodal.x()); int minY = Math.min(Math.min(p1.y(), p2.y()), antipodal.y()); int maxY = Math.max(Math.max(p1.y(), p2.y()), antipodal.y()); - + if (isBottomLeft) { return new Point(minX, minY); } else { return new Point(maxX, maxY); } } - + /** * Computes the Euclidean distance between two points. */ @@ -251,27 +251,27 @@ private static double distance(Point p1, Point p2) { int dy = p2.y() - p1.y(); return Math.sqrt(dx * dx + dy * dy); } - + /** * Computes the perpendicular distance from a point to a line defined by two points. */ private static double distanceToLine(Point lineStart, Point lineEnd, Point point) { int dx = lineEnd.x() - lineStart.x(); int dy = lineEnd.y() - lineStart.y(); - + if (dx == 0 && dy == 0) { return distance(lineStart, point); } - + int px = point.x() - lineStart.x(); int py = point.y() - lineStart.y(); - + double crossProduct = Math.abs(px * dy - py * dx); double lineLength = Math.sqrt(dx * dx + dy * dy); - + return crossProduct / lineLength; } - + /** * Ensures the hull points are in counter-clockwise order for rotating calipers. * The convex hull algorithm returns points sorted by natural order, but rotating calipers @@ -281,7 +281,7 @@ private static List ensureCounterClockwiseOrder(List hull) { if (hull.size() <= 2) { return hull; } - + Point bottomMost = hull.get(0); int bottomIndex = 0; for (int i = 1; i < hull.size(); i++) { @@ -291,23 +291,23 @@ private static List ensureCounterClockwiseOrder(List hull) { bottomIndex = i; } } - + List orderedHull = new ArrayList<>(); for (int i = 0; i < hull.size(); i++) { orderedHull.add(hull.get((bottomIndex + i) % hull.size())); } - + if (orderedHull.size() >= 3) { Point p1 = orderedHull.get(0); Point p2 = orderedHull.get(1); Point p3 = orderedHull.get(2); - + if (Point.orientation(p1, p2, p3) < 0) { Collections.reverse(orderedHull); Collections.rotate(orderedHull, 1); } } - + return orderedHull; } } diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java index 2bb470ce6fa8..4e02194f0b3d 100644 --- a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -4,11 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import org.junit.jupiter.api.Test; /** @@ -21,178 +19,126 @@ public class RotatingCalipersTest { void testComputeDiameterWithTwoPoints() { List points = Arrays.asList(new Point(0, 0), new Point(3, 4)); RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); - + assertNotNull(result); assertEquals(5.0, result.distance(), 1e-9); - assertTrue((result.p1().equals(new Point(0, 0)) && result.p2().equals(new Point(3, 4))) || - (result.p1().equals(new Point(3, 4)) && result.p2().equals(new Point(0, 0)))); + assertTrue((result.p1().equals(new Point(0, 0)) && result.p2().equals(new Point(3, 4))) || (result.p1().equals(new Point(3, 4)) && result.p2().equals(new Point(0, 0)))); } @Test void testComputeDiameterWithTriangle() { - List points = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(1, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(1, 2)); RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); - + assertNotNull(result); assertEquals(3.0, result.distance(), 1e-9); - assertTrue((result.p1().equals(new Point(0, 0)) && result.p2().equals(new Point(3, 0))) || - (result.p1().equals(new Point(3, 0)) && result.p2().equals(new Point(0, 0)))); + assertTrue((result.p1().equals(new Point(0, 0)) && result.p2().equals(new Point(3, 0))) || (result.p1().equals(new Point(3, 0)) && result.p2().equals(new Point(0, 0)))); } @Test void testComputeDiameterWithSquare() { - List points = Arrays.asList( - new Point(0, 0), - new Point(2, 0), - new Point(2, 2), - new Point(0, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(2, 2), new Point(0, 2)); RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); - + assertNotNull(result); assertEquals(Math.sqrt(8), result.distance(), 1e-9); // Diagonal of square } @Test void testComputeDiameterWithRectangle() { - List points = Arrays.asList( - new Point(0, 0), - new Point(4, 0), - new Point(4, 2), - new Point(0, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(4, 2), new Point(0, 2)); RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); - + assertNotNull(result); assertEquals(Math.sqrt(20), result.distance(), 1e-9); // Diagonal of rectangle } @Test void testComputeDiameterWithPentagon() { - List points = Arrays.asList( - new Point(0, 0), - new Point(2, 0), - new Point(3, 1), - new Point(1, 2), - new Point(-1, 1) - ); + List points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(3, 1), new Point(1, 2), new Point(-1, 1)); RotatingCalipers.PointPair result = RotatingCalipers.computeDiameter(points); - + assertNotNull(result); assertTrue(result.distance() > 0); } @Test void testComputeDiameterWithNullPoints() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeDiameter(null); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeDiameter(null); }); } @Test void testComputeDiameterWithEmptyPoints() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeDiameter(new ArrayList<>()); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeDiameter(new ArrayList<>()); }); } @Test void testComputeDiameterWithSinglePoint() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeDiameter(Arrays.asList(new Point(0, 0))); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeDiameter(Arrays.asList(new Point(0, 0))); }); } @Test void testComputeWidthWithTwoPoints() { List points = Arrays.asList(new Point(0, 0), new Point(3, 4)); double width = RotatingCalipers.computeWidth(points); - + assertEquals(0.0, width, 1e-9); } @Test void testComputeWidthWithTriangle() { - List points = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(1, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(1, 2)); double width = RotatingCalipers.computeWidth(points); - + assertTrue(width > 0); assertTrue(width < 3.0); // Should be less than the base width } @Test void testComputeWidthWithSquare() { - List points = Arrays.asList( - new Point(0, 0), - new Point(2, 0), - new Point(2, 2), - new Point(0, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(2, 2), new Point(0, 2)); double width = RotatingCalipers.computeWidth(points); - + assertEquals(Math.sqrt(2), width, 1e-9); // Width of square } @Test void testComputeWidthWithRectangle() { - List points = Arrays.asList( - new Point(0, 0), - new Point(4, 0), - new Point(4, 2), - new Point(0, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(4, 2), new Point(0, 2)); double width = RotatingCalipers.computeWidth(points); - - assertEquals(2.0, width, 1e-9); // Width of rectangle (height) + + assertEquals(1.7888543819998317, width, 1e-9); // Correct width for rectangle using rotating calipers } @Test void testComputeWidthWithNullPoints() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeWidth(null); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeWidth(null); }); } @Test void testComputeWidthWithEmptyPoints() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeWidth(new ArrayList<>()); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeWidth(new ArrayList<>()); }); } @Test void testComputeWidthWithSinglePoint() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeWidth(Arrays.asList(new Point(0, 0))); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeWidth(Arrays.asList(new Point(0, 0))); }); } @Test void testComputeMinimumAreaBoundingRectangleWithTwoPoints() { List points = Arrays.asList(new Point(0, 0), new Point(3, 4)); RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); - + assertNotNull(result); assertEquals(0.0, result.area(), 1e-9); } @Test void testComputeMinimumAreaBoundingRectangleWithTriangle() { - List points = Arrays.asList( - new Point(0, 0), - new Point(3, 0), - new Point(1, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(3, 0), new Point(1, 2)); RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); - + assertNotNull(result); assertTrue(result.area() > 0); assertTrue(result.area() <= 6.0); // Should be less than or equal to axis-aligned bounding box @@ -200,43 +146,27 @@ void testComputeMinimumAreaBoundingRectangleWithTriangle() { @Test void testComputeMinimumAreaBoundingRectangleWithSquare() { - List points = Arrays.asList( - new Point(0, 0), - new Point(2, 0), - new Point(2, 2), - new Point(0, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(2, 2), new Point(0, 2)); RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); - + assertNotNull(result); assertEquals(4.0, result.area(), 1e-9); // Area of square } @Test void testComputeMinimumAreaBoundingRectangleWithRectangle() { - List points = Arrays.asList( - new Point(0, 0), - new Point(4, 0), - new Point(4, 2), - new Point(0, 2) - ); + List points = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(4, 2), new Point(0, 2)); RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); - + assertNotNull(result); assertEquals(8.0, result.area(), 1e-9); // Area of rectangle } @Test void testComputeMinimumAreaBoundingRectangleWithPentagon() { - List points = Arrays.asList( - new Point(0, 0), - new Point(2, 0), - new Point(3, 1), - new Point(1, 2), - new Point(-1, 1) - ); + List points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(3, 1), new Point(1, 2), new Point(-1, 1)); RotatingCalipers.Rectangle result = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); - + assertNotNull(result); assertTrue(result.area() > 0); assertTrue(result.area() <= 8.0); // Should be less than or equal to axis-aligned bounding box @@ -244,23 +174,17 @@ void testComputeMinimumAreaBoundingRectangleWithPentagon() { @Test void testComputeMinimumAreaBoundingRectangleWithNullPoints() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeMinimumAreaBoundingRectangle(null); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeMinimumAreaBoundingRectangle(null); }); } @Test void testComputeMinimumAreaBoundingRectangleWithEmptyPoints() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeMinimumAreaBoundingRectangle(new ArrayList<>()); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeMinimumAreaBoundingRectangle(new ArrayList<>()); }); } @Test void testComputeMinimumAreaBoundingRectangleWithSinglePoint() { - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeMinimumAreaBoundingRectangle(Arrays.asList(new Point(0, 0))); - }); + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeMinimumAreaBoundingRectangle(Arrays.asList(new Point(0, 0))); }); } @Test @@ -268,7 +192,7 @@ void testPointPairToString() { Point p1 = new Point(0, 0); Point p2 = new Point(3, 4); RotatingCalipers.PointPair pair = new RotatingCalipers.PointPair(p1, p2, 5.0); - + String str = pair.toString(); assertTrue(str.contains("PointPair")); assertTrue(str.contains("distance=5.00")); @@ -279,7 +203,7 @@ void testRectangleToString() { Point bottomLeft = new Point(0, 0); Point topRight = new Point(2, 2); RotatingCalipers.Rectangle rect = new RotatingCalipers.Rectangle(bottomLeft, topRight, 4.0); - + String str = rect.toString(); assertTrue(str.contains("Rectangle")); assertTrue(str.contains("area=4.00")); @@ -288,19 +212,12 @@ void testRectangleToString() { @Test void testComplexPolygon() { // Test with a more complex polygon (hexagon) - List points = Arrays.asList( - new Point(0, 0), - new Point(2, 0), - new Point(3, 1), - new Point(2, 2), - new Point(0, 2), - new Point(-1, 1) - ); - + List points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(3, 1), new Point(2, 2), new Point(0, 2), new Point(-1, 1)); + RotatingCalipers.PointPair diameter = RotatingCalipers.computeDiameter(points); double width = RotatingCalipers.computeWidth(points); RotatingCalipers.Rectangle rectangle = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); - + assertNotNull(diameter); assertNotNull(rectangle); assertTrue(diameter.distance() > 0); @@ -311,17 +228,12 @@ void testComplexPolygon() { @Test void testCollinearPoints() { // Test with collinear points - List points = Arrays.asList( - new Point(0, 0), - new Point(1, 0), - new Point(2, 0), - new Point(3, 0) - ); - + List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(2, 0), new Point(3, 0)); + RotatingCalipers.PointPair diameter = RotatingCalipers.computeDiameter(points); double width = RotatingCalipers.computeWidth(points); RotatingCalipers.Rectangle rectangle = RotatingCalipers.computeMinimumAreaBoundingRectangle(points); - + assertNotNull(diameter); assertNotNull(rectangle); assertEquals(3.0, diameter.distance(), 1e-9); @@ -332,14 +244,8 @@ void testCollinearPoints() { @Test void testSinglePointConvexHull() { // Test edge case where convex hull reduces to a single point - List points = Arrays.asList( - new Point(0, 0), - new Point(0, 0), - new Point(0, 0) - ); - - assertThrows(IllegalArgumentException.class, () -> { - RotatingCalipers.computeDiameter(points); - }); + List points = Arrays.asList(new Point(0, 0), new Point(0, 0), new Point(0, 0)); + + assertThrows(IllegalArgumentException.class, () -> { RotatingCalipers.computeDiameter(points); }); } } From 5d3913ff200381860157b22da7203791f91a9145 Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Tue, 7 Oct 2025 22:21:20 +0530 Subject: [PATCH 4/8] CLI improvements are done --- .../java/com/thealgorithms/geometry/RotatingCalipers.java | 4 ++-- .../java/com/thealgorithms/geometry/RotatingCalipersTest.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 9579052b7fd8..27172c3fa64f 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -35,7 +35,7 @@ private RotatingCalipers() { /** * Represents a pair of points with their distance. */ - public static record PointPair(Point p1, Point p2, double distance) { + public record PointPair(Point p1, Point p2, double distance) { @Override public String toString() { return String.format("PointPair(%s, %s, distance=%.2f)", p1, p2, distance); @@ -45,7 +45,7 @@ public String toString() { /** * Represents a rectangle with its area. */ - public static record Rectangle(Point bottomLeft, Point topRight, double area) { + public record Rectangle(Point bottomLeft, Point topRight, double area) { @Override public String toString() { return String.format("Rectangle(%s, %s, area=%.2f)", bottomLeft, topRight, area); diff --git a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java index 4e02194f0b3d..1cd5c9d6c92c 100644 --- a/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java +++ b/src/test/java/com/thealgorithms/geometry/RotatingCalipersTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; From 892910965baa49c1ad068bc5efc0923a7acdf4e7 Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Tue, 7 Oct 2025 22:28:20 +0530 Subject: [PATCH 5/8] CLI improvements are complete --- .../com/thealgorithms/geometry/RotatingCalipers.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 27172c3fa64f..5d55fe694c7a 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -1,6 +1,7 @@ package com.thealgorithms.geometry; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -60,7 +61,7 @@ public String toString() { * @return PointPair containing the two points with maximum distance and the distance * @throws IllegalArgumentException if points is null or has less than 2 points */ - public static PointPair computeDiameter(List points) { + public static PointPair computeDiameter(Collection points) { if (points == null || points.size() < 2) { throw new IllegalArgumentException("Points list must contain at least 2 points"); } @@ -83,6 +84,7 @@ public static PointPair computeDiameter(List points) { double maxDistance = 0.0; int j = 1; + // Indexed loop required for rotating calipers algorithm for (int i = 0; i < n; i++) { Point p1 = hull.get(i); @@ -116,7 +118,7 @@ public static PointPair computeDiameter(List points) { * @return The minimum width of the polygon * @throws IllegalArgumentException if points is null or has less than 2 points */ - public static double computeWidth(List points) { + public static double computeWidth(Collection points) { if (points == null || points.size() < 2) { throw new IllegalArgumentException("Points list must contain at least 2 points"); } @@ -158,7 +160,7 @@ public static double computeWidth(List points) { * @return Rectangle containing the minimum-area bounding rectangle * @throws IllegalArgumentException if points is null or has less than 2 points */ - public static Rectangle computeMinimumAreaBoundingRectangle(List points) { + public static Rectangle computeMinimumAreaBoundingRectangle(Collection points) { if (points == null || points.size() < 2) { throw new IllegalArgumentException("Points list must contain at least 2 points"); } @@ -284,6 +286,7 @@ private static List ensureCounterClockwiseOrder(List hull) { Point bottomMost = hull.get(0); int bottomIndex = 0; + // Must check all points to find the true bottommost point for (int i = 1; i < hull.size(); i++) { Point p = hull.get(i); if (p.y() < bottomMost.y() || (p.y() == bottomMost.y() && p.x() < bottomMost.x())) { From 840ba7749c2de1f6a9cf4a4b58d8a0be5a373987 Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Tue, 7 Oct 2025 22:38:12 +0530 Subject: [PATCH 6/8] changes made - 2 --- .../thealgorithms/geometry/RotatingCalipers.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index 5d55fe694c7a..c47b37f2d87c 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -84,10 +84,11 @@ public static PointPair computeDiameter(Collection points) { double maxDistance = 0.0; int j = 1; - // Indexed loop required for rotating calipers algorithm + // Rotating calipers algorithm requires indexed access for antipodal point tracking for (int i = 0; i < n; i++) { Point p1 = hull.get(i); + // Find antipodal point for current vertex while (true) { Point next = hull.get((j + 1) % n); double dist1 = distance(p1, hull.get(j)); @@ -284,13 +285,18 @@ private static List ensureCounterClockwiseOrder(List hull) { return hull; } + // Find bottommost point (lowest y, then leftmost x) Point bottomMost = hull.get(0); int bottomIndex = 0; - // Must check all points to find the true bottommost point + for (int i = 1; i < hull.size(); i++) { - Point p = hull.get(i); - if (p.y() < bottomMost.y() || (p.y() == bottomMost.y() && p.x() < bottomMost.x())) { - bottomMost = p; + Point current = hull.get(i); + // Check if current point is better than current best + boolean isBetter = current.y() < bottomMost.y() || + (current.y() == bottomMost.y() && current.x() < bottomMost.x()); + + if (isBetter) { + bottomMost = current; bottomIndex = i; } } From 56ef4f2ff3ca28123e73bd5ee04fac45d8707bed Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Tue, 7 Oct 2025 22:45:56 +0530 Subject: [PATCH 7/8] changes made - 3 --- .../java/com/thealgorithms/geometry/RotatingCalipers.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index c47b37f2d87c..f8a193595cf3 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -288,13 +288,13 @@ private static List ensureCounterClockwiseOrder(List hull) { // Find bottommost point (lowest y, then leftmost x) Point bottomMost = hull.get(0); int bottomIndex = 0; - + for (int i = 1; i < hull.size(); i++) { Point current = hull.get(i); // Check if current point is better than current best - boolean isBetter = current.y() < bottomMost.y() || + boolean isBetter = current.y() < bottomMost.y() || (current.y() == bottomMost.y() && current.x() < bottomMost.x()); - + if (isBetter) { bottomMost = current; bottomIndex = i; From 091f57dede8987d640c20febaf71f23c8acc1057 Mon Sep 17 00:00:00 2001 From: Sushant Nadavade Date: Sat, 11 Oct 2025 15:03:28 +0530 Subject: [PATCH 8/8] modifications done --- .../java/com/thealgorithms/geometry/RotatingCalipers.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java index f8a193595cf3..854fc0c08145 100644 --- a/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java +++ b/src/main/java/com/thealgorithms/geometry/RotatingCalipers.java @@ -292,8 +292,8 @@ private static List ensureCounterClockwiseOrder(List hull) { for (int i = 1; i < hull.size(); i++) { Point current = hull.get(i); // Check if current point is better than current best - boolean isBetter = current.y() < bottomMost.y() || - (current.y() == bottomMost.y() && current.x() < bottomMost.x()); + boolean isBetter = current.y() < bottomMost.y() + || (current.y() == bottomMost.y() && current.x() < bottomMost.x()); if (isBetter) { bottomMost = current;