From 5a7f27cc33ec9b16319aefbb61edc9aa9e2e3e6a Mon Sep 17 00:00:00 2001 From: Microindole Date: Tue, 21 Oct 2025 10:34:28 +0800 Subject: [PATCH 1/8] feat(geometry): add Bentley-Ottmann line segment intersection algorithm - Implement sweep-line algorithm for finding all intersection points - Time complexity: O((n + k) log n) where n is segments, k is intersections - Uses event queue (PriorityQueue) and status structure (TreeSet) - Handles vertical/horizontal segments, collinear overlaps, and touching endpoints - Includes comprehensive Javadoc with examples and references --- .../geometry/BentleyOttmann.java | 409 ++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 src/main/java/com/thealgorithms/geometry/BentleyOttmann.java diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java new file mode 100644 index 000000000000..e838b9527302 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java @@ -0,0 +1,409 @@ +package com.thealgorithms.geometry; + +import java.awt.geom.Point2D; +import java.util.*; + +/** + * Implementation of the Bentley–Ottmann algorithm for finding all intersection + * points among a set of line segments in O((n + k) log n) time. + * + *

Uses a sweep-line approach with an event queue and status structure to + * efficiently detect intersections in 2D plane geometry.

+ * + * @see + * Bentley–Ottmann algorithm + */ +public final class BentleyOttmann { + + private BentleyOttmann() {} + + private static final double EPS = 1e-9; + private static double currentSweepX; + + /** + * Represents a line segment with two endpoints. + */ + public static class Segment { + final Point2D.Double p1; + final Point2D.Double p2; + final int id; // Unique identifier for each segment + + Segment(Point2D.Double p1, Point2D.Double p2) { + this.p1 = p1; + this.p2 = p2; + this.id = segmentCounter++; + } + + private static int segmentCounter = 0; + + /** + * Computes the y-coordinate of this segment at a given x value. + */ + double getY(double x) { + if (Math.abs(p2.x - p1.x) < EPS) { + // Vertical segment: return midpoint y + return (p1.y + p2.y) / 2.0; + } + double t = (x - p1.x) / (p2.x - p1.x); + return p1.y + t * (p2.y - p1.y); + } + + Point2D.Double leftPoint() { + return p1.x < p2.x ? p1 : (p1.x > p2.x ? p2 : (p1.y < p2.y ? p1 : p2)); + } + + Point2D.Double rightPoint() { + return p1.x > p2.x ? p1 : (p1.x < p2.x ? p2 : (p1.y > p2.y ? p1 : p2)); + } + + @Override + public String toString() { + return String.format("S%d[(%.2f, %.2f), (%.2f, %.2f)]", id, p1.x, p1.y, p2.x, p2.y); + } + } + + /** + * Event types for the sweep line algorithm. + */ + private enum EventType { + START, END, INTERSECTION + } + + /** + * Represents an event in the event queue. + */ + private static class Event implements Comparable { + final Point2D.Double point; + final EventType type; + final Set segments; // Segments involved in this event + + Event(Point2D.Double point, EventType type) { + this.point = point; + this.type = type; + this.segments = new HashSet<>(); + } + + void addSegment(Segment s) { + segments.add(s); + } + + @Override + public int compareTo(Event other) { + // Sort by x-coordinate, then by y-coordinate + int cmp = Double.compare(this.point.x, other.point.x); + if (cmp == 0) { + cmp = Double.compare(this.point.y, other.point.y); + } + if (cmp == 0) { + // Process END events before START events at same point + cmp = this.type.compareTo(other.type); + } + return cmp; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Event e)) return false; + return pointsEqual(this.point, e.point); + } + + @Override + public int hashCode() { + return Objects.hash(Math.round(point.x * 1e6), Math.round(point.y * 1e6)); + } + } + + /** + * Comparator for segments in the status structure (sweep line). + * Orders segments by their y-coordinate at the current sweep line position. + */ + private static class StatusComparator implements Comparator { + @Override + public int compare(Segment s1, Segment s2) { + if (s1.id == s2.id) return 0; + + double y1 = s1.getY(currentSweepX); + double y2 = s2.getY(currentSweepX); + + int cmp = Double.compare(y1, y2); + if (Math.abs(y1 - y2) < EPS) { + // If y-coordinates are equal, use segment id for consistency + return Integer.compare(s1.id, s2.id); + } + return cmp; + } + } + + /** + * Finds all intersection points among a set of line segments. + * + *

An intersection point is reported when two or more segments cross or touch. + * For overlapping segments, only actual crossing/touching points are reported, + * not all points along the overlap.

+ * + * @param segments list of line segments represented as pairs of points + * @return a set of intersection points where segments meet or cross + * @throws IllegalArgumentException if the list is null or contains null points + */ + public static Set findIntersections(List segments) { + if (segments == null) { + throw new IllegalArgumentException("Segment list must not be null"); + } + + Segment.segmentCounter = 0; // Reset counter + Set intersections = new HashSet<>(); + PriorityQueue eventQueue = new PriorityQueue<>(); + TreeSet status = new TreeSet<>(new StatusComparator()); + Map eventMap = new HashMap<>(); + + // Initialize event queue with segment start and end points + for (Segment s : segments) { + Point2D.Double left = s.leftPoint(); + Point2D.Double right = s.rightPoint(); + + Event startEvent = getOrCreateEvent(eventMap, left, EventType.START); + startEvent.addSegment(s); + + Event endEvent = getOrCreateEvent(eventMap, right, EventType.END); + endEvent.addSegment(s); + } + + // Add all unique events to the queue + for (Event e : eventMap.values()) { + if (!e.segments.isEmpty()) { + eventQueue.add(e); + } + } + + // Process events + while (!eventQueue.isEmpty()) { + Event event = eventQueue.poll(); + currentSweepX = event.point.x; + + handleEvent(event, status, eventQueue, eventMap, intersections); + } + + return intersections; + } + + private static Event getOrCreateEvent(Map eventMap, + Point2D.Double point, EventType type) { + // Find existing event at this point + for (Map.Entry entry : eventMap.entrySet()) { + if (pointsEqual(entry.getKey(), point)) { + return entry.getValue(); + } + } + // Create new event + Event event = new Event(point, type); + eventMap.put(point, event); + return event; + } + + private static void handleEvent(Event event, TreeSet status, + PriorityQueue eventQueue, + Map eventMap, + Set intersections) { + Point2D.Double p = event.point; + Set segmentsAtPoint = new HashSet<>(event.segments); + + // Check segments in status structure (much smaller than allSegments) + for (Segment s : status) { + if (pointsEqual(s.p1, p) || pointsEqual(s.p2, p) || + (onSegment(s, p) && !pointsEqual(s.p1, p) && !pointsEqual(s.p2, p))) { + segmentsAtPoint.add(s); + } + } + + // If 2 or more segments meet at this point, it's an intersection + if (segmentsAtPoint.size() >= 2) { + intersections.add(p); + } + + // Categorize segments + Set upperSegs = new HashSet<>(); // Segments starting at p + Set lowerSegs = new HashSet<>(); // Segments ending at p + Set containingSegs = new HashSet<>(); // Segments containing p in interior + + for (Segment s : segmentsAtPoint) { + if (pointsEqual(s.leftPoint(), p)) { + upperSegs.add(s); + } else if (pointsEqual(s.rightPoint(), p)) { + lowerSegs.add(s); + } else { + containingSegs.add(s); + } + } + + // Remove ending segments and segments containing p from status + status.removeAll(lowerSegs); + status.removeAll(containingSegs); + + // Update sweep line position slightly past the event + currentSweepX = p.x + EPS; + + // Add starting segments and re-add containing segments + status.addAll(upperSegs); + status.addAll(containingSegs); + + if (upperSegs.isEmpty() && containingSegs.isEmpty()) { + // Find neighbors and check for new intersections + Segment sl = getNeighbor(status, lowerSegs, true); + Segment sr = getNeighbor(status, lowerSegs, false); + if (sl != null && sr != null) { + findNewEvent(sl, sr, p, eventQueue, eventMap); + } + } else { + Set unionSegs = new HashSet<>(upperSegs); + unionSegs.addAll(containingSegs); + + Segment leftmost = getLeftmost(unionSegs, status); + Segment rightmost = getRightmost(unionSegs, status); + + if (leftmost != null) { + Segment sl = status.lower(leftmost); + if (sl != null) { + findNewEvent(sl, leftmost, p, eventQueue, eventMap); + } + } + + if (rightmost != null) { + Segment sr = status.higher(rightmost); + if (sr != null) { + findNewEvent(rightmost, sr, p, eventQueue, eventMap); + } + } + } + } + + private static Segment getNeighbor(TreeSet status, Set removed, boolean lower) { + if (removed.isEmpty()) return null; + Segment ref = removed.iterator().next(); + return lower ? status.lower(ref) : status.higher(ref); + } + + private static Segment getLeftmost(Set segments, TreeSet status) { + Segment leftmost = null; + for (Segment s : segments) { + if (leftmost == null || Objects.requireNonNull(status.comparator()).compare(s, leftmost) < 0) { + leftmost = s; + } + } + return leftmost; + } + + private static Segment getRightmost(Set segments, TreeSet status) { + Segment rightmost = null; + for (Segment s : segments) { + if (status.comparator() != null && (rightmost == null || status.comparator().compare(s, rightmost) > 0)) { + rightmost = s; + } + } + return rightmost; + } + + private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentPoint, + PriorityQueue eventQueue, + Map eventMap) { + Point2D.Double intersection = getIntersection(s1, s2); + + if (intersection != null && + intersection.x > currentPoint.x - EPS && + !pointsEqual(intersection, currentPoint)) { + + // Check if event already exists + boolean exists = false; + for (Point2D.Double p : eventMap.keySet()) { + if (pointsEqual(p, intersection)) { + exists = true; + Event existingEvent = eventMap.get(p); + existingEvent.addSegment(s1); + existingEvent.addSegment(s2); + break; + } + } + + if (!exists) { + Event newEvent = new Event(intersection, EventType.INTERSECTION); + newEvent.addSegment(s1); + newEvent.addSegment(s2); + eventMap.put(intersection, newEvent); + eventQueue.add(newEvent); + } + } + } + + private static Point2D.Double getIntersection(Segment s1, Segment s2) { + double x1 = s1.p1.x, y1 = s1.p1.y; + double x2 = s1.p2.x, y2 = s1.p2.y; + double x3 = s2.p1.x, y3 = s2.p1.y; + double x4 = s2.p2.x, y4 = s2.p2.y; + + double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + + if (Math.abs(denom) < EPS) { + // Parallel or collinear + if (areCollinear(s1, s2)) { + // For collinear segments, check if they overlap + // Return any overlapping point + List overlapPoints = new ArrayList<>(); + + if (onSegment(s1, s2.p1)) overlapPoints.add(s2.p1); + if (onSegment(s1, s2.p2)) overlapPoints.add(s2.p2); + if (onSegment(s2, s1.p1)) overlapPoints.add(s1.p1); + if (onSegment(s2, s1.p2)) overlapPoints.add(s1.p2); + + // Remove duplicates and return the first point + if (!overlapPoints.isEmpty()) { + // Find the point that's not an endpoint of both segments + for (Point2D.Double pt : overlapPoints) { + boolean isS1Endpoint = pointsEqual(pt, s1.p1) || pointsEqual(pt, s1.p2); + boolean isS2Endpoint = pointsEqual(pt, s2.p1) || pointsEqual(pt, s2.p2); + + // If it's an endpoint of both, it's a touching point + if (isS1Endpoint && isS2Endpoint) { + return pt; + } + } + // Return the first overlap point + return overlapPoints.getFirst(); + } + } + return null; + } + + double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom; + double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom; + + if (t >= -EPS && t <= 1 + EPS && u >= -EPS && u <= 1 + EPS) { + double px = x1 + t * (x2 - x1); + double py = y1 + t * (y2 - y1); + return new Point2D.Double(px, py); + } + + return null; + } + + private static boolean areCollinear(Segment s1, Segment s2) { + double cross1 = crossProduct(s1.p1, s1.p2, s2.p1); + double cross2 = crossProduct(s1.p1, s1.p2, s2.p2); + return Math.abs(cross1) < EPS && Math.abs(cross2) < EPS; + } + + private static double crossProduct(Point2D.Double a, Point2D.Double b, Point2D.Double c) { + return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); + } + + private static boolean onSegment(Segment s, Point2D.Double p) { + return p.x >= Math.min(s.p1.x, s.p2.x) - EPS && + p.x <= Math.max(s.p1.x, s.p2.x) + EPS && + p.y >= Math.min(s.p1.y, s.p2.y) - EPS && + p.y <= Math.max(s.p1.y, s.p2.y) + EPS && + Math.abs(crossProduct(s.p1, s.p2, p)) < EPS; + } + + private static boolean pointsEqual(Point2D.Double p1, Point2D.Double p2) { + return Math.abs(p1.x - p2.x) < EPS && Math.abs(p1.y - p2.y) < EPS; + } + +} \ No newline at end of file From dd086bd5c18b65003482ab5b57ec0f0ede366649 Mon Sep 17 00:00:00 2001 From: Microindole Date: Tue, 21 Oct 2025 10:35:09 +0800 Subject: [PATCH 2/8] test(geometry): add comprehensive tests for Bentley-Ottmann algorithm - 19 test cases covering typical, edge, and degenerate cases - Tests include: single/multiple intersections, parallel segments, grid patterns - Performance test with 100 random segments - All tests validate correctness of intersection detection --- .../geometry/BentleyOttmannTest.java | 343 ++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java new file mode 100644 index 000000000000..2c210f3cd76a --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -0,0 +1,343 @@ +package com.thealgorithms.geometry; + +import org.junit.jupiter.api.Test; +import java.awt.geom.Point2D; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Comprehensive unit tests for {@link BentleyOttmann}. + * + *

This test suite validates the correctness of the Bentley–Ottmann algorithm + * implementation by checking intersection points between multiple line segment configurations.

+ * + *

Test cases include typical, edge, degenerate geometrical setups, and performance tests.

+ */ +public class BentleyOttmannTest { + + private static final double EPS = 1e-6; + + @Test + void testSingleIntersection() { + List segments = List.of( + newSegment(1, 1, 5, 5), + newSegment(1, 5, 5, 1) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertEquals(1, intersections.size()); + assertTrue(containsPoint(intersections, 3.0, 3.0)); + } + + @Test + void testVerticalIntersection() { + List segments = List.of( + newSegment(3, 0, 3, 6), + newSegment(1, 1, 5, 5) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertEquals(1, intersections.size()); + assertTrue(containsPoint(intersections, 3.0, 3.0)); + } + + @Test + void testNoIntersection() { + List segments = List.of( + newSegment(0, 0, 1, 1), + newSegment(2, 2, 3, 3) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(intersections.isEmpty()); + } + + @Test + void testCoincidentSegments() { + List segments = List.of( + newSegment(1, 1, 5, 5), + newSegment(1, 1, 5, 5) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + + assertEquals(2, intersections.size(), "Two identical segments should report 2 intersection points (both endpoints)"); + assertTrue(containsPoint(intersections, 1.0, 1.0)); + assertTrue(containsPoint(intersections, 5.0, 5.0)); + } + + @Test + void testHorizontalIntersection() { + List segments = List.of( + newSegment(0, 2, 4, 2), + newSegment(2, 0, 2, 4) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testEmptyList() { + List segments = List.of(); + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(intersections.isEmpty()); + } + + @Test + void testSingleSegment() { + List segments = List.of( + newSegment(0, 0, 5, 5) + ); + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(intersections.isEmpty()); + } + + @Test + void testNullListThrowsException() { + assertThrows(IllegalArgumentException.class, () -> BentleyOttmann.findIntersections(null)); + } + + @Test + void testParallelSegments() { + // Test 1: Parallel diagonal segments + List diagonalSegments = List.of( + newSegment(0, 0, 4, 4), + newSegment(1, 0, 5, 4), + newSegment(2, 0, 6, 4) + ); + assertTrue(BentleyOttmann.findIntersections(cast(diagonalSegments)).isEmpty()); + + // Test 2: Parallel vertical segments + List verticalSegments = List.of( + newSegment(1, 0, 1, 5), + newSegment(2, 0, 2, 5), + newSegment(3, 0, 3, 5) + ); + assertTrue(BentleyOttmann.findIntersections(cast(verticalSegments)).isEmpty()); + + // Test 3: Parallel horizontal segments + List horizontalSegments = List.of( + newSegment(0, 1, 5, 1), + newSegment(0, 2, 5, 2), + newSegment(0, 3, 5, 3) + ); + assertTrue(BentleyOttmann.findIntersections(cast(horizontalSegments)).isEmpty()); + } + + @Test + void testTouchingEndpoints() { + List segments = List.of( + newSegment(0, 0, 2, 2), + newSegment(2, 2, 4, 0) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertEquals(1, intersections.size()); + assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testOverlappingCollinearSegments() { + List segments = List.of( + newSegment(0, 0, 4, 4), + newSegment(2, 2, 6, 6) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + // Overlapping collinear segments share the point (2,2) where second starts + // and (4,4) where first ends - at least one should be detected + assertFalse(intersections.isEmpty(), "Should find at least one overlap point"); + assertTrue(containsPoint(intersections, 2.0, 2.0) || + containsPoint(intersections, 4.0, 4.0), + "Should contain either (2,2) or (4,4)"); + } + + @Test + void testMultipleSegmentsAtOnePoint() { + // Star pattern: 4 segments meeting at (2, 2) + List segments = List.of( + newSegment(0, 2, 4, 2), // horizontal + newSegment(2, 0, 2, 4), // vertical + newSegment(0, 0, 4, 4), // diagonal / + newSegment(0, 4, 4, 0) // diagonal \ + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(containsPoint(intersections, 2.0, 2.0)); + // All segments meet at (2, 2), so should be reported once + assertEquals(1, intersections.size()); + } + + @Test + void testGridPattern() { + // 3x3 grid: should have 9 intersection points + List segments = new ArrayList<>(); + + // Vertical lines at x = 0, 1, 2 + for (int i = 0; i <= 2; i++) { + segments.add(newSegment(i, 0, i, 2)); + } + + // Horizontal lines at y = 0, 1, 2 + for (int i = 0; i <= 2; i++) { + segments.add(newSegment(0, i, 2, i)); + } + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + + // Each vertical line crosses each horizontal line + // 3 vertical × 3 horizontal = 9 intersections + assertEquals(9, intersections.size(), "3x3 grid should have 9 intersections"); + + // Verify all grid points are present + for (int x = 0; x <= 2; x++) { + for (int y = 0; y <= 2; y++) { + assertTrue(containsPoint(intersections, x, y), + String.format("Grid point (%d, %d) should be present", x, y)); + } + } + } + + @Test + void testTriangleIntersections() { + // Three segments forming a triangle + List segments = List.of( + newSegment(0, 0, 4, 0), // base + newSegment(0, 0, 2, 3), // left side + newSegment(4, 0, 2, 3) // right side + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + // Triangle vertices are intersections + assertTrue(containsPoint(intersections, 0.0, 0.0)); + assertTrue(containsPoint(intersections, 4.0, 0.0)); + assertTrue(containsPoint(intersections, 2.0, 3.0)); + assertEquals(3, intersections.size()); + } + + @Test + void testCrossingDiagonals() { + // X pattern with multiple crossings + List segments = List.of( + newSegment(0, 0, 10, 10), + newSegment(0, 10, 10, 0), + newSegment(5, 0, 5, 10), + newSegment(0, 5, 10, 5) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(containsPoint(intersections, 5.0, 5.0), "Center point should be present"); + assertEquals(1, intersections.size()); + } + + + + @Test + void testVerySmallSegments() { + List segments = List.of( + newSegment(0.001, 0.001, 0.002, 0.002), + newSegment(0.001, 0.002, 0.002, 0.001) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertEquals(1, intersections.size()); + assertTrue(containsPoint(intersections, 0.0015, 0.0015)); + } + + @Test + void testSegmentsShareCommonPoint() { + List segmentsSameStart = List.of( + newSegment(0, 0, 4, 4), + newSegment(0, 0, 4, -4), + newSegment(0, 0, -4, 4) + ); + + Set intersectionsSameStart = BentleyOttmann.findIntersections(cast(segmentsSameStart)); + assertTrue(containsPoint(intersectionsSameStart, 0.0, 0.0)); + List segmentsSameEnd = List.of( + newSegment(0, 0, 4, 4), + newSegment(8, 4, 4, 4), + newSegment(4, 8, 4, 4) + ); + + Set intersectionsSameEnd = BentleyOttmann.findIntersections(cast(segmentsSameEnd)); + assertTrue(containsPoint(intersectionsSameEnd, 4.0, 4.0)); + } + + @Test + void testSegmentsAtAngles() { + // Segments at 45, 90, 135 degrees + List segments = List.of( + newSegment(0, 2, 4, 2), // horizontal + newSegment(2, 0, 2, 4), // vertical + newSegment(0, 0, 4, 4), // 45 degrees + newSegment(0, 4, 4, 0) // 135 degrees + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testPerformanceWithManySegments() { + // Generate 100 random segments + Random random = new Random(42); // Fixed seed for reproducibility + List segments = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + double x1 = random.nextDouble() * 100; + double y1 = random.nextDouble() * 100; + double x2 = random.nextDouble() * 100; + double y2 = random.nextDouble() * 100; + segments.add(newSegment(x1, y1, x2, y2)); + } + + long startTime = System.currentTimeMillis(); + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + long endTime = System.currentTimeMillis(); + + long duration = endTime - startTime; + + // Should complete in reasonable time (< 1 second for 100 segments) + assertTrue(duration < 1000, + "Algorithm should complete in less than 1 second for 100 segments. Took: " + duration + "ms"); + + // Just verify it returns a valid result + assertNotNull(intersections); + System.out.println("Performance test: 100 segments processed in " + duration + + "ms, found " + intersections.size() + " intersections"); + } + + @Test + void testIssueExample() { + // Example from the GitHub issue + List segments = List.of( + newSegment(1, 1, 5, 5), // Segment A + newSegment(1, 5, 5, 1), // Segment B + newSegment(3, 0, 3, 6) // Segment C + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + + // Expected output: [(3, 3)] + assertEquals(1, intersections.size(), "Should find exactly one intersection"); + assertTrue(containsPoint(intersections, 3.0, 3.0), + "Intersection should be at (3, 3)"); + } + + private static Object newSegment(double x1, double y1, double x2, double y2) { + return new BentleyOttmann.Segment(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); + } + + private static List cast(List objs) { + List result = new ArrayList<>(); + for (Object o : objs) result.add((BentleyOttmann.Segment) o); + return result; + } + + private static boolean containsPoint(Set points, double x, double y) { + return points.stream().anyMatch(p -> Math.abs(p.x - x) < EPS && Math.abs(p.y - y) < EPS); + } +} \ No newline at end of file From adc3197ed12d57357cde07e641721d3f20b65d48 Mon Sep 17 00:00:00 2001 From: Microindole Date: Tue, 21 Oct 2025 10:54:52 +0800 Subject: [PATCH 3/8] style(geometry): fix code style --- .../geometry/BentleyOttmann.java | 35 ++--- .../geometry/BentleyOttmannTest.java | 135 ++++++------------ 2 files changed, 51 insertions(+), 119 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java index e838b9527302..a35348694739 100644 --- a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java +++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java @@ -15,7 +15,8 @@ */ public final class BentleyOttmann { - private BentleyOttmann() {} + private BentleyOttmann() { + } private static final double EPS = 1e-9; private static double currentSweepX; @@ -65,9 +66,7 @@ public String toString() { /** * Event types for the sweep line algorithm. */ - private enum EventType { - START, END, INTERSECTION - } + private enum EventType { START, END, INTERSECTION } /** * Represents an event in the event queue. @@ -186,8 +185,7 @@ public static Set findIntersections(List segments) { return intersections; } - private static Event getOrCreateEvent(Map eventMap, - Point2D.Double point, EventType type) { + private static Event getOrCreateEvent(Map eventMap, Point2D.Double point, EventType type) { // Find existing event at this point for (Map.Entry entry : eventMap.entrySet()) { if (pointsEqual(entry.getKey(), point)) { @@ -200,17 +198,13 @@ private static Event getOrCreateEvent(Map eventMap, return event; } - private static void handleEvent(Event event, TreeSet status, - PriorityQueue eventQueue, - Map eventMap, - Set intersections) { + private static void handleEvent(Event event, TreeSet status, PriorityQueue eventQueue, Map eventMap, Set intersections) { Point2D.Double p = event.point; Set segmentsAtPoint = new HashSet<>(event.segments); // Check segments in status structure (much smaller than allSegments) for (Segment s : status) { - if (pointsEqual(s.p1, p) || pointsEqual(s.p2, p) || - (onSegment(s, p) && !pointsEqual(s.p1, p) && !pointsEqual(s.p2, p))) { + if (pointsEqual(s.p1, p) || pointsEqual(s.p2, p) || (onSegment(s, p) && !pointsEqual(s.p1, p) && !pointsEqual(s.p2, p))) { segmentsAtPoint.add(s); } } @@ -302,14 +296,10 @@ private static Segment getRightmost(Set segments, TreeSet stat return rightmost; } - private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentPoint, - PriorityQueue eventQueue, - Map eventMap) { + private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentPoint, PriorityQueue eventQueue, Map eventMap) { Point2D.Double intersection = getIntersection(s1, s2); - if (intersection != null && - intersection.x > currentPoint.x - EPS && - !pointsEqual(intersection, currentPoint)) { + if (intersection != null && intersection.x > currentPoint.x - EPS && !pointsEqual(intersection, currentPoint)) { // Check if event already exists boolean exists = false; @@ -395,15 +385,10 @@ private static double crossProduct(Point2D.Double a, Point2D.Double b, Point2D.D } private static boolean onSegment(Segment s, Point2D.Double p) { - return p.x >= Math.min(s.p1.x, s.p2.x) - EPS && - p.x <= Math.max(s.p1.x, s.p2.x) + EPS && - p.y >= Math.min(s.p1.y, s.p2.y) - EPS && - p.y <= Math.max(s.p1.y, s.p2.y) + EPS && - Math.abs(crossProduct(s.p1, s.p2, p)) < EPS; + return p.x >= Math.min(s.p1.x, s.p2.x) - EPS && p.x <= Math.max(s.p1.x, s.p2.x) + EPS && p.y >= Math.min(s.p1.y, s.p2.y) - EPS && p.y <= Math.max(s.p1.y, s.p2.y) + EPS && Math.abs(crossProduct(s.p1, s.p2, p)) < EPS; } private static boolean pointsEqual(Point2D.Double p1, Point2D.Double p2) { return Math.abs(p1.x - p2.x) < EPS && Math.abs(p1.y - p2.y) < EPS; } - -} \ No newline at end of file +} diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java index 2c210f3cd76a..1b9037988f34 100644 --- a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -1,11 +1,14 @@ package com.thealgorithms.geometry; -import org.junit.jupiter.api.Test; -import java.awt.geom.Point2D; -import java.util.*; - import static org.junit.jupiter.api.Assertions.*; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; +import org.junit.jupiter.api.Test; + /** * Comprehensive unit tests for {@link BentleyOttmann}. * @@ -20,10 +23,7 @@ public class BentleyOttmannTest { @Test void testSingleIntersection() { - List segments = List.of( - newSegment(1, 1, 5, 5), - newSegment(1, 5, 5, 1) - ); + List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 5, 5, 1)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertEquals(1, intersections.size()); @@ -32,10 +32,7 @@ void testSingleIntersection() { @Test void testVerticalIntersection() { - List segments = List.of( - newSegment(3, 0, 3, 6), - newSegment(1, 1, 5, 5) - ); + List segments = List.of(newSegment(3, 0, 3, 6), newSegment(1, 1, 5, 5)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertEquals(1, intersections.size()); @@ -44,10 +41,7 @@ void testVerticalIntersection() { @Test void testNoIntersection() { - List segments = List.of( - newSegment(0, 0, 1, 1), - newSegment(2, 2, 3, 3) - ); + List segments = List.of(newSegment(0, 0, 1, 1), newSegment(2, 2, 3, 3)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertTrue(intersections.isEmpty()); @@ -69,10 +63,7 @@ void testCoincidentSegments() { @Test void testHorizontalIntersection() { - List segments = List.of( - newSegment(0, 2, 4, 2), - newSegment(2, 0, 2, 4) - ); + List segments = List.of(newSegment(0, 2, 4, 2), newSegment(2, 0, 2, 4)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertTrue(containsPoint(intersections, 2.0, 2.0)); @@ -87,9 +78,7 @@ void testEmptyList() { @Test void testSingleSegment() { - List segments = List.of( - newSegment(0, 0, 5, 5) - ); + List segments = List.of(newSegment(0, 0, 5, 5)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertTrue(intersections.isEmpty()); } @@ -102,36 +91,21 @@ void testNullListThrowsException() { @Test void testParallelSegments() { // Test 1: Parallel diagonal segments - List diagonalSegments = List.of( - newSegment(0, 0, 4, 4), - newSegment(1, 0, 5, 4), - newSegment(2, 0, 6, 4) - ); + List diagonalSegments = List.of(newSegment(0, 0, 4, 4), newSegment(1, 0, 5, 4), newSegment(2, 0, 6, 4)); assertTrue(BentleyOttmann.findIntersections(cast(diagonalSegments)).isEmpty()); // Test 2: Parallel vertical segments - List verticalSegments = List.of( - newSegment(1, 0, 1, 5), - newSegment(2, 0, 2, 5), - newSegment(3, 0, 3, 5) - ); + List verticalSegments = List.of(newSegment(1, 0, 1, 5), newSegment(2, 0, 2, 5), newSegment(3, 0, 3, 5)); assertTrue(BentleyOttmann.findIntersections(cast(verticalSegments)).isEmpty()); // Test 3: Parallel horizontal segments - List horizontalSegments = List.of( - newSegment(0, 1, 5, 1), - newSegment(0, 2, 5, 2), - newSegment(0, 3, 5, 3) - ); + List horizontalSegments = List.of(newSegment(0, 1, 5, 1), newSegment(0, 2, 5, 2), newSegment(0, 3, 5, 3)); assertTrue(BentleyOttmann.findIntersections(cast(horizontalSegments)).isEmpty()); } @Test void testTouchingEndpoints() { - List segments = List.of( - newSegment(0, 0, 2, 2), - newSegment(2, 2, 4, 0) - ); + List segments = List.of(newSegment(0, 0, 2, 2), newSegment(2, 2, 4, 0)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertEquals(1, intersections.size()); @@ -140,28 +114,22 @@ void testTouchingEndpoints() { @Test void testOverlappingCollinearSegments() { - List segments = List.of( - newSegment(0, 0, 4, 4), - newSegment(2, 2, 6, 6) - ); + List segments = List.of(newSegment(0, 0, 4, 4), newSegment(2, 2, 6, 6)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); // Overlapping collinear segments share the point (2,2) where second starts // and (4,4) where first ends - at least one should be detected assertFalse(intersections.isEmpty(), "Should find at least one overlap point"); - assertTrue(containsPoint(intersections, 2.0, 2.0) || - containsPoint(intersections, 4.0, 4.0), - "Should contain either (2,2) or (4,4)"); + assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain either (2,2) or (4,4)"); } @Test void testMultipleSegmentsAtOnePoint() { // Star pattern: 4 segments meeting at (2, 2) - List segments = List.of( - newSegment(0, 2, 4, 2), // horizontal - newSegment(2, 0, 2, 4), // vertical - newSegment(0, 0, 4, 4), // diagonal / - newSegment(0, 4, 4, 0) // diagonal \ + List segments = List.of(newSegment(0, 2, 4, 2), // horizontal + newSegment(2, 0, 2, 4), // vertical + newSegment(0, 0, 4, 4), // diagonal / + newSegment(0, 4, 4, 0) // diagonal \ ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); @@ -194,9 +162,7 @@ void testGridPattern() { // Verify all grid points are present for (int x = 0; x <= 2; x++) { for (int y = 0; y <= 2; y++) { - assertTrue(containsPoint(intersections, x, y), - String.format("Grid point (%d, %d) should be present", x, y)); - } + assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y)); } } } @@ -204,9 +170,9 @@ void testGridPattern() { void testTriangleIntersections() { // Three segments forming a triangle List segments = List.of( - newSegment(0, 0, 4, 0), // base - newSegment(0, 0, 2, 3), // left side - newSegment(4, 0, 2, 3) // right side + newSegment(0, 0, 4, 0), // base + newSegment(0, 0, 2, 3), // left side + newSegment(4, 0, 2, 3) // right side ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); @@ -220,12 +186,7 @@ void testTriangleIntersections() { @Test void testCrossingDiagonals() { // X pattern with multiple crossings - List segments = List.of( - newSegment(0, 0, 10, 10), - newSegment(0, 10, 10, 0), - newSegment(5, 0, 5, 10), - newSegment(0, 5, 10, 5) - ); + List segments = List.of(newSegment(0, 0, 10, 10), newSegment(0, 10, 10, 0), newSegment(5, 0, 5, 10), newSegment(0, 5, 10, 5)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertTrue(containsPoint(intersections, 5.0, 5.0), "Center point should be present"); @@ -236,10 +197,7 @@ void testCrossingDiagonals() { @Test void testVerySmallSegments() { - List segments = List.of( - newSegment(0.001, 0.001, 0.002, 0.002), - newSegment(0.001, 0.002, 0.002, 0.001) - ); + List segments = List.of(newSegment(0.001, 0.001, 0.002, 0.002), newSegment(0.001, 0.002, 0.002, 0.001)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); assertEquals(1, intersections.size()); @@ -248,19 +206,11 @@ void testVerySmallSegments() { @Test void testSegmentsShareCommonPoint() { - List segmentsSameStart = List.of( - newSegment(0, 0, 4, 4), - newSegment(0, 0, 4, -4), - newSegment(0, 0, -4, 4) - ); + List segmentsSameStart = List.of(newSegment(0, 0, 4, 4), newSegment(0, 0, 4, -4), newSegment(0, 0, -4, 4)); Set intersectionsSameStart = BentleyOttmann.findIntersections(cast(segmentsSameStart)); assertTrue(containsPoint(intersectionsSameStart, 0.0, 0.0)); - List segmentsSameEnd = List.of( - newSegment(0, 0, 4, 4), - newSegment(8, 4, 4, 4), - newSegment(4, 8, 4, 4) - ); + List segmentsSameEnd = List.of(newSegment(0, 0, 4, 4), newSegment(8, 4, 4, 4), newSegment(4, 8, 4, 4)); Set intersectionsSameEnd = BentleyOttmann.findIntersections(cast(segmentsSameEnd)); assertTrue(containsPoint(intersectionsSameEnd, 4.0, 4.0)); @@ -270,10 +220,10 @@ void testSegmentsShareCommonPoint() { void testSegmentsAtAngles() { // Segments at 45, 90, 135 degrees List segments = List.of( - newSegment(0, 2, 4, 2), // horizontal - newSegment(2, 0, 2, 4), // vertical - newSegment(0, 0, 4, 4), // 45 degrees - newSegment(0, 4, 4, 0) // 135 degrees + newSegment(0, 2, 4, 2), // horizontal + newSegment(2, 0, 2, 4), // vertical + newSegment(0, 0, 4, 4), // 45 degrees + newSegment(0, 4, 4, 0) // 135 degrees ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); @@ -301,30 +251,27 @@ void testPerformanceWithManySegments() { long duration = endTime - startTime; // Should complete in reasonable time (< 1 second for 100 segments) - assertTrue(duration < 1000, - "Algorithm should complete in less than 1 second for 100 segments. Took: " + duration + "ms"); + assertTrue(duration < 1000, "Algorithm should complete in less than 1 second for 100 segments. Took: " + duration + "ms"); // Just verify it returns a valid result assertNotNull(intersections); - System.out.println("Performance test: 100 segments processed in " + duration + - "ms, found " + intersections.size() + " intersections"); + System.out.println("Performance test: 100 segments processed in " + duration + "ms, found " + intersections.size() + " intersections"); } @Test void testIssueExample() { // Example from the GitHub issue List segments = List.of( - newSegment(1, 1, 5, 5), // Segment A - newSegment(1, 5, 5, 1), // Segment B - newSegment(3, 0, 3, 6) // Segment C + newSegment(1, 1, 5, 5), // Segment A + newSegment(1, 5, 5, 1), // Segment B + newSegment(3, 0, 3, 6) // Segment C ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); // Expected output: [(3, 3)] assertEquals(1, intersections.size(), "Should find exactly one intersection"); - assertTrue(containsPoint(intersections, 3.0, 3.0), - "Intersection should be at (3, 3)"); + assertTrue(containsPoint(intersections, 3.0, 3.0), "Intersection should be at (3, 3)"); } private static Object newSegment(double x1, double y1, double x2, double y2) { @@ -340,4 +287,4 @@ private static List cast(List objs) { private static boolean containsPoint(Set points, double x, double y) { return points.stream().anyMatch(p -> Math.abs(p.x - x) < EPS && Math.abs(p.y - y) < EPS); } -} \ No newline at end of file +} From 29c806b246d7ccadfe80a2c34df9ab1b80c5a3c8 Mon Sep 17 00:00:00 2001 From: Microindole Date: Tue, 21 Oct 2025 16:10:25 +0800 Subject: [PATCH 4/8] test(geometry): Achieve 100% test coverage for BentleyOttmann --- .../geometry/BentleyOttmann.java | 53 +++++++++---- .../geometry/BentleyOttmannTest.java | 76 +++++++++++++++---- 2 files changed, 103 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java index a35348694739..5bab351e0e77 100644 --- a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java +++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java @@ -1,7 +1,16 @@ package com.thealgorithms.geometry; import java.awt.geom.Point2D; -import java.util.*; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.List; +import java.util.PriorityQueue; +import java.util.TreeSet; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; /** * Implementation of the Bentley–Ottmann algorithm for finding all intersection @@ -102,7 +111,9 @@ public int compareTo(Event other) { @Override public boolean equals(Object o) { - if (!(o instanceof Event e)) return false; + if (!(o instanceof Event e)) { + return false; + } return pointsEqual(this.point, e.point); } @@ -116,10 +127,12 @@ public int hashCode() { * Comparator for segments in the status structure (sweep line). * Orders segments by their y-coordinate at the current sweep line position. */ - private static class StatusComparator implements Comparator { + private final static class StatusComparator implements Comparator { @Override public int compare(Segment s1, Segment s2) { - if (s1.id == s2.id) return 0; + if (s1.id == s2.id) { + return 0; + } double y1 = s1.getY(currentSweepX); double y2 = s2.getY(currentSweepX); @@ -271,7 +284,9 @@ private static void handleEvent(Event event, TreeSet status, PriorityQu } private static Segment getNeighbor(TreeSet status, Set removed, boolean lower) { - if (removed.isEmpty()) return null; + if (removed.isEmpty()) { + return null; + } Segment ref = removed.iterator().next(); return lower ? status.lower(ref) : status.higher(ref); } @@ -324,10 +339,14 @@ private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentP } private static Point2D.Double getIntersection(Segment s1, Segment s2) { - double x1 = s1.p1.x, y1 = s1.p1.y; - double x2 = s1.p2.x, y2 = s1.p2.y; - double x3 = s2.p1.x, y3 = s2.p1.y; - double x4 = s2.p2.x, y4 = s2.p2.y; + double x1 = s1.p1.x; + double y1 = s1.p1.y; + double x2 = s1.p2.x; + double y2 = s1.p2.y; + double x3 = s2.p1.x; + double y3 = s2.p1.y; + double x4 = s2.p2.x; + double y4 = s2.p2.y; double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); @@ -338,10 +357,18 @@ private static Point2D.Double getIntersection(Segment s1, Segment s2) { // Return any overlapping point List overlapPoints = new ArrayList<>(); - if (onSegment(s1, s2.p1)) overlapPoints.add(s2.p1); - if (onSegment(s1, s2.p2)) overlapPoints.add(s2.p2); - if (onSegment(s2, s1.p1)) overlapPoints.add(s1.p1); - if (onSegment(s2, s1.p2)) overlapPoints.add(s1.p2); + if (onSegment(s1, s2.p1)) { + overlapPoints.add(s2.p1); + } + if (onSegment(s1, s2.p2)) { + overlapPoints.add(s2.p2); + } + if (onSegment(s2, s1.p1)) { + overlapPoints.add(s1.p1); + } + if (onSegment(s2, s1.p2)) { + overlapPoints.add(s1.p2); + } // Remove duplicates and return the first point if (!overlapPoints.isEmpty()) { diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java index 1b9037988f34..293aa460c65e 100644 --- a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -1,6 +1,10 @@ package com.thealgorithms.geometry; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -49,10 +53,7 @@ void testNoIntersection() { @Test void testCoincidentSegments() { - List segments = List.of( - newSegment(1, 1, 5, 5), - newSegment(1, 1, 5, 5) - ); + List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 1, 5, 5)); Set intersections = BentleyOttmann.findIntersections(cast(segments)); @@ -162,15 +163,15 @@ void testGridPattern() { // Verify all grid points are present for (int x = 0; x <= 2; x++) { for (int y = 0; y <= 2; y++) { - assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y)); } + assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y)); + } } } @Test void testTriangleIntersections() { // Three segments forming a triangle - List segments = List.of( - newSegment(0, 0, 4, 0), // base + List segments = List.of(newSegment(0, 0, 4, 0), // base newSegment(0, 0, 2, 3), // left side newSegment(4, 0, 2, 3) // right side ); @@ -219,8 +220,7 @@ void testSegmentsShareCommonPoint() { @Test void testSegmentsAtAngles() { // Segments at 45, 90, 135 degrees - List segments = List.of( - newSegment(0, 2, 4, 2), // horizontal + List segments = List.of(newSegment(0, 2, 4, 2), // horizontal newSegment(2, 0, 2, 4), // vertical newSegment(0, 0, 4, 4), // 45 degrees newSegment(0, 4, 4, 0) // 135 degrees @@ -261,8 +261,7 @@ void testPerformanceWithManySegments() { @Test void testIssueExample() { // Example from the GitHub issue - List segments = List.of( - newSegment(1, 1, 5, 5), // Segment A + List segments = List.of(newSegment(1, 1, 5, 5), // Segment A newSegment(1, 5, 5, 1), // Segment B newSegment(3, 0, 3, 6) // Segment C ); @@ -274,13 +273,64 @@ void testIssueExample() { assertTrue(containsPoint(intersections, 3.0, 3.0), "Intersection should be at (3, 3)"); } + @Test + void testEventTypeOrdering() { + // Multiple events at the same point with different types + List segments = List.of( + newSegment(2, 2, 6, 2), // ends at (2,2) + newSegment(0, 2, 2, 2), // ends at (2,2) + newSegment(2, 2, 2, 6), // starts at (2,2) + newSegment(2, 0, 2, 2) // ends at (2,2) + ); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testCollinearOverlapWithInteriorPoint() { + // Test collinear segments where one segment's interior overlaps another + List segments = List.of(newSegment(0, 0, 6, 6), newSegment(2, 2, 4, 4)); + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + + // Should find at least one overlap point (where segments touch/overlap) + assertFalse(intersections.isEmpty(), "Should find overlap points for collinear segments"); + assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain overlap boundary point"); + } + + @Test + void testCollinearTouchingAtBothEndpoints() { + // Test collinear segments that touch at both endpoints + // This triggers the "endpoint of both" logic (line 354-355) + List segments = List.of(newSegment(0, 0, 4, 4), newSegment(4, 4, 8, 8)); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + assertEquals(1, intersections.size()); + assertTrue(containsPoint(intersections, 4.0, 4.0), "Should find touching point"); + } + + @Test + void testCollinearOverlapPartialInterior() { + // Test case where segments overlap but one point is inside, one is endpoint + List segments = List.of(newSegment(0, 0, 5, 5), newSegment(3, 3, 7, 7)); + + Set intersections = BentleyOttmann.findIntersections(cast(segments)); + + // Should detect the overlap region + assertFalse(intersections.isEmpty()); + // The algorithm should return at least one of the boundary points + assertTrue(containsPoint(intersections, 3.0, 3.0) || containsPoint(intersections, 5.0, 5.0)); + } + private static Object newSegment(double x1, double y1, double x2, double y2) { return new BentleyOttmann.Segment(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); } private static List cast(List objs) { List result = new ArrayList<>(); - for (Object o : objs) result.add((BentleyOttmann.Segment) o); + for (Object o : objs) { + result.add((BentleyOttmann.Segment) o); + } return result; } From 405647ae41145c97ff2ab720bc1d9d4597a5f94d Mon Sep 17 00:00:00 2001 From: Microindole Date: Tue, 21 Oct 2025 16:22:53 +0800 Subject: [PATCH 5/8] style(geometry): fix code style again --- .../geometry/BentleyOttmann.java | 2 +- .../geometry/BentleyOttmannTest.java | 29 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java index 5bab351e0e77..a03f8e30f544 100644 --- a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java +++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java @@ -127,7 +127,7 @@ public int hashCode() { * Comparator for segments in the status structure (sweep line). * Orders segments by their y-coordinate at the current sweep line position. */ - private final static class StatusComparator implements Comparator { + private static final class StatusComparator implements Comparator { @Override public int compare(Segment s1, Segment s2) { if (s1.id == s2.id) { diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java index 293aa460c65e..8ad69a96ec5b 100644 --- a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -1,10 +1,10 @@ package com.thealgorithms.geometry; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertFalse; 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.awt.geom.Point2D; import java.util.ArrayList; @@ -172,8 +172,8 @@ void testGridPattern() { void testTriangleIntersections() { // Three segments forming a triangle List segments = List.of(newSegment(0, 0, 4, 0), // base - newSegment(0, 0, 2, 3), // left side - newSegment(4, 0, 2, 3) // right side + newSegment(0, 0, 2, 3), // left side + newSegment(4, 0, 2, 3) // right side ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); @@ -194,8 +194,6 @@ void testCrossingDiagonals() { assertEquals(1, intersections.size()); } - - @Test void testVerySmallSegments() { List segments = List.of(newSegment(0.001, 0.001, 0.002, 0.002), newSegment(0.001, 0.002, 0.002, 0.001)); @@ -221,9 +219,9 @@ void testSegmentsShareCommonPoint() { void testSegmentsAtAngles() { // Segments at 45, 90, 135 degrees List segments = List.of(newSegment(0, 2, 4, 2), // horizontal - newSegment(2, 0, 2, 4), // vertical - newSegment(0, 0, 4, 4), // 45 degrees - newSegment(0, 4, 4, 0) // 135 degrees + newSegment(2, 0, 2, 4), // vertical + newSegment(0, 0, 4, 4), // 45 degrees + newSegment(0, 4, 4, 0) // 135 degrees ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); @@ -262,8 +260,8 @@ void testPerformanceWithManySegments() { void testIssueExample() { // Example from the GitHub issue List segments = List.of(newSegment(1, 1, 5, 5), // Segment A - newSegment(1, 5, 5, 1), // Segment B - newSegment(3, 0, 3, 6) // Segment C + newSegment(1, 5, 5, 1), // Segment B + newSegment(3, 0, 3, 6) // Segment C ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); @@ -276,11 +274,10 @@ void testIssueExample() { @Test void testEventTypeOrdering() { // Multiple events at the same point with different types - List segments = List.of( - newSegment(2, 2, 6, 2), // ends at (2,2) - newSegment(0, 2, 2, 2), // ends at (2,2) - newSegment(2, 2, 2, 6), // starts at (2,2) - newSegment(2, 0, 2, 2) // ends at (2,2) + List segments = List.of(newSegment(2, 2, 6, 2), // ends at (2,2) + newSegment(0, 2, 2, 2), // ends at (2,2) + newSegment(2, 2, 2, 6), // starts at (2,2) + newSegment(2, 0, 2, 2) // ends at (2,2) ); Set intersections = BentleyOttmann.findIntersections(cast(segments)); From 29e5224307ef45b0ca93c44455eb405420695e06 Mon Sep 17 00:00:00 2001 From: Microindole Date: Tue, 21 Oct 2025 23:21:54 +0800 Subject: [PATCH 6/8] fix: correct import order in BentleyOttmann --- .../geometry/BentleyOttmann.java | 24 ++-- .../geometry/BentleyOttmannTest.java | 114 ++++++++---------- 2 files changed, 66 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java index a03f8e30f544..d2c39b27a61e 100644 --- a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java +++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java @@ -1,16 +1,18 @@ package com.thealgorithms.geometry; import java.awt.geom.Point2D; +import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; -import java.util.Objects; -import java.util.Set; import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Objects; import java.util.PriorityQueue; +import java.util.Set; +import java.util.SortedSet; import java.util.TreeSet; -import java.util.Map; -import java.util.HashMap; -import java.util.ArrayList; /** * Implementation of the Bentley–Ottmann algorithm for finding all intersection @@ -283,7 +285,7 @@ private static void handleEvent(Event event, TreeSet status, PriorityQu } } - private static Segment getNeighbor(TreeSet status, Set removed, boolean lower) { + private static Segment getNeighbor(NavigableSet status, Set removed, boolean lower) { if (removed.isEmpty()) { return null; } @@ -291,7 +293,7 @@ private static Segment getNeighbor(TreeSet status, Set removed return lower ? status.lower(ref) : status.higher(ref); } - private static Segment getLeftmost(Set segments, TreeSet status) { + private static Segment getLeftmost(Set segments, SortedSet status) { Segment leftmost = null; for (Segment s : segments) { if (leftmost == null || Objects.requireNonNull(status.comparator()).compare(s, leftmost) < 0) { @@ -301,7 +303,7 @@ private static Segment getLeftmost(Set segments, TreeSet statu return leftmost; } - private static Segment getRightmost(Set segments, TreeSet status) { + private static Segment getRightmost(Set segments, SortedSet status) { Segment rightmost = null; for (Segment s : segments) { if (status.comparator() != null && (rightmost == null || status.comparator().compare(s, rightmost) > 0)) { @@ -318,10 +320,10 @@ private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentP // Check if event already exists boolean exists = false; - for (Point2D.Double p : eventMap.keySet()) { - if (pointsEqual(p, intersection)) { + for (Map.Entry entry : eventMap.entrySet()) { + if (pointsEqual(entry.getKey(), intersection)) { exists = true; - Event existingEvent = eventMap.get(p); + Event existingEvent = entry.getValue(); existingEvent.addSegment(s1); existingEvent.addSegment(s2); break; diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java index 8ad69a96ec5b..9bd03ef1b79e 100644 --- a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -27,35 +27,35 @@ public class BentleyOttmannTest { @Test void testSingleIntersection() { - List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 5, 5, 1)); + List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 5, 5, 1)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertEquals(1, intersections.size()); assertTrue(containsPoint(intersections, 3.0, 3.0)); } @Test void testVerticalIntersection() { - List segments = List.of(newSegment(3, 0, 3, 6), newSegment(1, 1, 5, 5)); + List segments = List.of(newSegment(3, 0, 3, 6), newSegment(1, 1, 5, 5)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertEquals(1, intersections.size()); assertTrue(containsPoint(intersections, 3.0, 3.0)); } @Test void testNoIntersection() { - List segments = List.of(newSegment(0, 0, 1, 1), newSegment(2, 2, 3, 3)); + List segments = List.of(newSegment(0, 0, 1, 1), newSegment(2, 2, 3, 3)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(intersections.isEmpty()); } @Test void testCoincidentSegments() { - List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 1, 5, 5)); + List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 1, 5, 5)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertEquals(2, intersections.size(), "Two identical segments should report 2 intersection points (both endpoints)"); assertTrue(containsPoint(intersections, 1.0, 1.0)); @@ -64,23 +64,23 @@ void testCoincidentSegments() { @Test void testHorizontalIntersection() { - List segments = List.of(newSegment(0, 2, 4, 2), newSegment(2, 0, 2, 4)); + List segments = List.of(newSegment(0, 2, 4, 2), newSegment(2, 0, 2, 4)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(containsPoint(intersections, 2.0, 2.0)); } @Test void testEmptyList() { - List segments = List.of(); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + List segments = List.of(); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(intersections.isEmpty()); } @Test void testSingleSegment() { - List segments = List.of(newSegment(0, 0, 5, 5)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + List segments = List.of(newSegment(0, 0, 5, 5)); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(intersections.isEmpty()); } @@ -92,32 +92,32 @@ void testNullListThrowsException() { @Test void testParallelSegments() { // Test 1: Parallel diagonal segments - List diagonalSegments = List.of(newSegment(0, 0, 4, 4), newSegment(1, 0, 5, 4), newSegment(2, 0, 6, 4)); - assertTrue(BentleyOttmann.findIntersections(cast(diagonalSegments)).isEmpty()); + List diagonalSegments = List.of(newSegment(0, 0, 4, 4), newSegment(1, 0, 5, 4), newSegment(2, 0, 6, 4)); + assertTrue(BentleyOttmann.findIntersections(diagonalSegments).isEmpty()); // Test 2: Parallel vertical segments - List verticalSegments = List.of(newSegment(1, 0, 1, 5), newSegment(2, 0, 2, 5), newSegment(3, 0, 3, 5)); - assertTrue(BentleyOttmann.findIntersections(cast(verticalSegments)).isEmpty()); + List verticalSegments = List.of(newSegment(1, 0, 1, 5), newSegment(2, 0, 2, 5), newSegment(3, 0, 3, 5)); + assertTrue(BentleyOttmann.findIntersections(verticalSegments).isEmpty()); // Test 3: Parallel horizontal segments - List horizontalSegments = List.of(newSegment(0, 1, 5, 1), newSegment(0, 2, 5, 2), newSegment(0, 3, 5, 3)); - assertTrue(BentleyOttmann.findIntersections(cast(horizontalSegments)).isEmpty()); + List horizontalSegments = List.of(newSegment(0, 1, 5, 1), newSegment(0, 2, 5, 2), newSegment(0, 3, 5, 3)); + assertTrue(BentleyOttmann.findIntersections(horizontalSegments).isEmpty()); } @Test void testTouchingEndpoints() { - List segments = List.of(newSegment(0, 0, 2, 2), newSegment(2, 2, 4, 0)); + List segments = List.of(newSegment(0, 0, 2, 2), newSegment(2, 2, 4, 0)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertEquals(1, intersections.size()); assertTrue(containsPoint(intersections, 2.0, 2.0)); } @Test void testOverlappingCollinearSegments() { - List segments = List.of(newSegment(0, 0, 4, 4), newSegment(2, 2, 6, 6)); + List segments = List.of(newSegment(0, 0, 4, 4), newSegment(2, 2, 6, 6)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); // Overlapping collinear segments share the point (2,2) where second starts // and (4,4) where first ends - at least one should be detected assertFalse(intersections.isEmpty(), "Should find at least one overlap point"); @@ -127,13 +127,13 @@ void testOverlappingCollinearSegments() { @Test void testMultipleSegmentsAtOnePoint() { // Star pattern: 4 segments meeting at (2, 2) - List segments = List.of(newSegment(0, 2, 4, 2), // horizontal + List segments = List.of(newSegment(0, 2, 4, 2), // horizontal newSegment(2, 0, 2, 4), // vertical newSegment(0, 0, 4, 4), // diagonal / newSegment(0, 4, 4, 0) // diagonal \ ); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(containsPoint(intersections, 2.0, 2.0)); // All segments meet at (2, 2), so should be reported once assertEquals(1, intersections.size()); @@ -142,7 +142,7 @@ void testMultipleSegmentsAtOnePoint() { @Test void testGridPattern() { // 3x3 grid: should have 9 intersection points - List segments = new ArrayList<>(); + List segments = new ArrayList<>(); // Vertical lines at x = 0, 1, 2 for (int i = 0; i <= 2; i++) { @@ -154,7 +154,7 @@ void testGridPattern() { segments.add(newSegment(0, i, 2, i)); } - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); // Each vertical line crosses each horizontal line // 3 vertical × 3 horizontal = 9 intersections @@ -171,12 +171,12 @@ void testGridPattern() { @Test void testTriangleIntersections() { // Three segments forming a triangle - List segments = List.of(newSegment(0, 0, 4, 0), // base + List segments = List.of(newSegment(0, 0, 4, 0), // base newSegment(0, 0, 2, 3), // left side newSegment(4, 0, 2, 3) // right side ); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); // Triangle vertices are intersections assertTrue(containsPoint(intersections, 0.0, 0.0)); assertTrue(containsPoint(intersections, 4.0, 0.0)); @@ -187,44 +187,44 @@ void testTriangleIntersections() { @Test void testCrossingDiagonals() { // X pattern with multiple crossings - List segments = List.of(newSegment(0, 0, 10, 10), newSegment(0, 10, 10, 0), newSegment(5, 0, 5, 10), newSegment(0, 5, 10, 5)); + List segments = List.of(newSegment(0, 0, 10, 10), newSegment(0, 10, 10, 0), newSegment(5, 0, 5, 10), newSegment(0, 5, 10, 5)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(containsPoint(intersections, 5.0, 5.0), "Center point should be present"); assertEquals(1, intersections.size()); } @Test void testVerySmallSegments() { - List segments = List.of(newSegment(0.001, 0.001, 0.002, 0.002), newSegment(0.001, 0.002, 0.002, 0.001)); + List segments = List.of(newSegment(0.001, 0.001, 0.002, 0.002), newSegment(0.001, 0.002, 0.002, 0.001)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertEquals(1, intersections.size()); assertTrue(containsPoint(intersections, 0.0015, 0.0015)); } @Test void testSegmentsShareCommonPoint() { - List segmentsSameStart = List.of(newSegment(0, 0, 4, 4), newSegment(0, 0, 4, -4), newSegment(0, 0, -4, 4)); + List segmentsSameStart = List.of(newSegment(0, 0, 4, 4), newSegment(0, 0, 4, -4), newSegment(0, 0, -4, 4)); - Set intersectionsSameStart = BentleyOttmann.findIntersections(cast(segmentsSameStart)); + Set intersectionsSameStart = BentleyOttmann.findIntersections(segmentsSameStart); assertTrue(containsPoint(intersectionsSameStart, 0.0, 0.0)); - List segmentsSameEnd = List.of(newSegment(0, 0, 4, 4), newSegment(8, 4, 4, 4), newSegment(4, 8, 4, 4)); + List segmentsSameEnd = List.of(newSegment(0, 0, 4, 4), newSegment(8, 4, 4, 4), newSegment(4, 8, 4, 4)); - Set intersectionsSameEnd = BentleyOttmann.findIntersections(cast(segmentsSameEnd)); + Set intersectionsSameEnd = BentleyOttmann.findIntersections(segmentsSameEnd); assertTrue(containsPoint(intersectionsSameEnd, 4.0, 4.0)); } @Test void testSegmentsAtAngles() { // Segments at 45, 90, 135 degrees - List segments = List.of(newSegment(0, 2, 4, 2), // horizontal + List segments = List.of(newSegment(0, 2, 4, 2), // horizontal newSegment(2, 0, 2, 4), // vertical newSegment(0, 0, 4, 4), // 45 degrees newSegment(0, 4, 4, 0) // 135 degrees ); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(containsPoint(intersections, 2.0, 2.0)); } @@ -232,7 +232,7 @@ void testSegmentsAtAngles() { void testPerformanceWithManySegments() { // Generate 100 random segments Random random = new Random(42); // Fixed seed for reproducibility - List segments = new ArrayList<>(); + List segments = new ArrayList<>(); for (int i = 0; i < 100; i++) { double x1 = random.nextDouble() * 100; @@ -243,7 +243,7 @@ void testPerformanceWithManySegments() { } long startTime = System.currentTimeMillis(); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); long endTime = System.currentTimeMillis(); long duration = endTime - startTime; @@ -259,12 +259,12 @@ void testPerformanceWithManySegments() { @Test void testIssueExample() { // Example from the GitHub issue - List segments = List.of(newSegment(1, 1, 5, 5), // Segment A + List segments = List.of(newSegment(1, 1, 5, 5), // Segment A newSegment(1, 5, 5, 1), // Segment B newSegment(3, 0, 3, 6) // Segment C ); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); // Expected output: [(3, 3)] assertEquals(1, intersections.size(), "Should find exactly one intersection"); @@ -274,21 +274,21 @@ void testIssueExample() { @Test void testEventTypeOrdering() { // Multiple events at the same point with different types - List segments = List.of(newSegment(2, 2, 6, 2), // ends at (2,2) + List segments = List.of(newSegment(2, 2, 6, 2), // ends at (2,2) newSegment(0, 2, 2, 2), // ends at (2,2) newSegment(2, 2, 2, 6), // starts at (2,2) newSegment(2, 0, 2, 2) // ends at (2,2) ); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertTrue(containsPoint(intersections, 2.0, 2.0)); } @Test void testCollinearOverlapWithInteriorPoint() { // Test collinear segments where one segment's interior overlaps another - List segments = List.of(newSegment(0, 0, 6, 6), newSegment(2, 2, 4, 4)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + List segments = List.of(newSegment(0, 0, 6, 6), newSegment(2, 2, 4, 4)); + Set intersections = BentleyOttmann.findIntersections(segments); // Should find at least one overlap point (where segments touch/overlap) assertFalse(intersections.isEmpty(), "Should find overlap points for collinear segments"); @@ -299,9 +299,9 @@ void testCollinearOverlapWithInteriorPoint() { void testCollinearTouchingAtBothEndpoints() { // Test collinear segments that touch at both endpoints // This triggers the "endpoint of both" logic (line 354-355) - List segments = List.of(newSegment(0, 0, 4, 4), newSegment(4, 4, 8, 8)); + List segments = List.of(newSegment(0, 0, 4, 4), newSegment(4, 4, 8, 8)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); assertEquals(1, intersections.size()); assertTrue(containsPoint(intersections, 4.0, 4.0), "Should find touching point"); } @@ -309,9 +309,9 @@ void testCollinearTouchingAtBothEndpoints() { @Test void testCollinearOverlapPartialInterior() { // Test case where segments overlap but one point is inside, one is endpoint - List segments = List.of(newSegment(0, 0, 5, 5), newSegment(3, 3, 7, 7)); + List segments = List.of(newSegment(0, 0, 5, 5), newSegment(3, 3, 7, 7)); - Set intersections = BentleyOttmann.findIntersections(cast(segments)); + Set intersections = BentleyOttmann.findIntersections(segments); // Should detect the overlap region assertFalse(intersections.isEmpty()); @@ -319,18 +319,10 @@ void testCollinearOverlapPartialInterior() { assertTrue(containsPoint(intersections, 3.0, 3.0) || containsPoint(intersections, 5.0, 5.0)); } - private static Object newSegment(double x1, double y1, double x2, double y2) { + private static BentleyOttmann.Segment newSegment(double x1, double y1, double x2, double y2) { return new BentleyOttmann.Segment(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); } - private static List cast(List objs) { - List result = new ArrayList<>(); - for (Object o : objs) { - result.add((BentleyOttmann.Segment) o); - } - return result; - } - private static boolean containsPoint(Set points, double x, double y) { return points.stream().anyMatch(p -> Math.abs(p.x - x) < EPS && Math.abs(p.y - y) < EPS); } From 47b36e4be5453adcde427938cd549db83458bc01 Mon Sep 17 00:00:00 2001 From: Microindole Date: Wed, 22 Oct 2025 08:12:42 +0800 Subject: [PATCH 7/8] fix(geometry): Resolve SpotBugs and PMD static analysis warnings --- .../geometry/BentleyOttmann.java | 4 +- .../geometry/BentleyOttmannTest.java | 96 +++++++++---------- 2 files changed, 48 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java index d2c39b27a61e..a11acb87dd7a 100644 --- a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java +++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java @@ -61,11 +61,11 @@ public static class Segment { } Point2D.Double leftPoint() { - return p1.x < p2.x ? p1 : (p1.x > p2.x ? p2 : (p1.y < p2.y ? p1 : p2)); + return p1.x < p2.x ? p1 : p1.x > p2.x ? p2 : p1.y < p2.y ? p1 : p2; } Point2D.Double rightPoint() { - return p1.x > p2.x ? p1 : (p1.x < p2.x ? p2 : (p1.y > p2.y ? p1 : p2)); + return p1.x > p2.x ? p1 : p1.x < p2.x ? p2 : p1.y > p2.y ? p1 : p2; } @Override diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java index 9bd03ef1b79e..008fdf8a7ce9 100644 --- a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -1,10 +1,6 @@ package com.thealgorithms.geometry; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -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 org.junit.jupiter.api.Assertions; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -30,8 +26,8 @@ void testSingleIntersection() { List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 5, 5, 1)); Set intersections = BentleyOttmann.findIntersections(segments); - assertEquals(1, intersections.size()); - assertTrue(containsPoint(intersections, 3.0, 3.0)); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0)); } @Test @@ -39,8 +35,8 @@ void testVerticalIntersection() { List segments = List.of(newSegment(3, 0, 3, 6), newSegment(1, 1, 5, 5)); Set intersections = BentleyOttmann.findIntersections(segments); - assertEquals(1, intersections.size()); - assertTrue(containsPoint(intersections, 3.0, 3.0)); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0)); } @Test @@ -48,7 +44,7 @@ void testNoIntersection() { List segments = List.of(newSegment(0, 0, 1, 1), newSegment(2, 2, 3, 3)); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(intersections.isEmpty()); + Assertions.assertTrue(intersections.isEmpty()); } @Test @@ -57,9 +53,9 @@ void testCoincidentSegments() { Set intersections = BentleyOttmann.findIntersections(segments); - assertEquals(2, intersections.size(), "Two identical segments should report 2 intersection points (both endpoints)"); - assertTrue(containsPoint(intersections, 1.0, 1.0)); - assertTrue(containsPoint(intersections, 5.0, 5.0)); + Assertions.assertEquals(2, intersections.size(), "Two identical segments should report 2 intersection points (both endpoints)"); + Assertions.assertTrue(containsPoint(intersections, 1.0, 1.0)); + Assertions.assertTrue(containsPoint(intersections, 5.0, 5.0)); } @Test @@ -67,41 +63,41 @@ void testHorizontalIntersection() { List segments = List.of(newSegment(0, 2, 4, 2), newSegment(2, 0, 2, 4)); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(containsPoint(intersections, 2.0, 2.0)); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); } @Test void testEmptyList() { List segments = List.of(); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(intersections.isEmpty()); + Assertions.assertTrue(intersections.isEmpty()); } @Test void testSingleSegment() { List segments = List.of(newSegment(0, 0, 5, 5)); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(intersections.isEmpty()); + Assertions.assertTrue(intersections.isEmpty()); } @Test void testNullListThrowsException() { - assertThrows(IllegalArgumentException.class, () -> BentleyOttmann.findIntersections(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> BentleyOttmann.findIntersections(null)); } @Test void testParallelSegments() { // Test 1: Parallel diagonal segments List diagonalSegments = List.of(newSegment(0, 0, 4, 4), newSegment(1, 0, 5, 4), newSegment(2, 0, 6, 4)); - assertTrue(BentleyOttmann.findIntersections(diagonalSegments).isEmpty()); + Assertions.assertTrue(BentleyOttmann.findIntersections(diagonalSegments).isEmpty()); // Test 2: Parallel vertical segments List verticalSegments = List.of(newSegment(1, 0, 1, 5), newSegment(2, 0, 2, 5), newSegment(3, 0, 3, 5)); - assertTrue(BentleyOttmann.findIntersections(verticalSegments).isEmpty()); + Assertions.assertTrue(BentleyOttmann.findIntersections(verticalSegments).isEmpty()); // Test 3: Parallel horizontal segments List horizontalSegments = List.of(newSegment(0, 1, 5, 1), newSegment(0, 2, 5, 2), newSegment(0, 3, 5, 3)); - assertTrue(BentleyOttmann.findIntersections(horizontalSegments).isEmpty()); + Assertions.assertTrue(BentleyOttmann.findIntersections(horizontalSegments).isEmpty()); } @Test @@ -109,8 +105,8 @@ void testTouchingEndpoints() { List segments = List.of(newSegment(0, 0, 2, 2), newSegment(2, 2, 4, 0)); Set intersections = BentleyOttmann.findIntersections(segments); - assertEquals(1, intersections.size()); - assertTrue(containsPoint(intersections, 2.0, 2.0)); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); } @Test @@ -120,8 +116,8 @@ void testOverlappingCollinearSegments() { Set intersections = BentleyOttmann.findIntersections(segments); // Overlapping collinear segments share the point (2,2) where second starts // and (4,4) where first ends - at least one should be detected - assertFalse(intersections.isEmpty(), "Should find at least one overlap point"); - assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain either (2,2) or (4,4)"); + Assertions.assertFalse(intersections.isEmpty(), "Should find at least one overlap point"); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain either (2,2) or (4,4)"); } @Test @@ -134,9 +130,9 @@ void testMultipleSegmentsAtOnePoint() { ); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(containsPoint(intersections, 2.0, 2.0)); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); // All segments meet at (2, 2), so should be reported once - assertEquals(1, intersections.size()); + Assertions.assertEquals(1, intersections.size()); } @Test @@ -158,12 +154,12 @@ void testGridPattern() { // Each vertical line crosses each horizontal line // 3 vertical × 3 horizontal = 9 intersections - assertEquals(9, intersections.size(), "3x3 grid should have 9 intersections"); + Assertions.assertEquals(9, intersections.size(), "3x3 grid should have 9 intersections"); // Verify all grid points are present for (int x = 0; x <= 2; x++) { for (int y = 0; y <= 2; y++) { - assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y)); + Assertions.assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y)); } } } @@ -178,10 +174,10 @@ void testTriangleIntersections() { Set intersections = BentleyOttmann.findIntersections(segments); // Triangle vertices are intersections - assertTrue(containsPoint(intersections, 0.0, 0.0)); - assertTrue(containsPoint(intersections, 4.0, 0.0)); - assertTrue(containsPoint(intersections, 2.0, 3.0)); - assertEquals(3, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 0.0, 0.0)); + Assertions.assertTrue(containsPoint(intersections, 4.0, 0.0)); + Assertions.assertTrue(containsPoint(intersections, 2.0, 3.0)); + Assertions.assertEquals(3, intersections.size()); } @Test @@ -190,8 +186,8 @@ void testCrossingDiagonals() { List segments = List.of(newSegment(0, 0, 10, 10), newSegment(0, 10, 10, 0), newSegment(5, 0, 5, 10), newSegment(0, 5, 10, 5)); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(containsPoint(intersections, 5.0, 5.0), "Center point should be present"); - assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 5.0, 5.0), "Center point should be present"); + Assertions.assertEquals(1, intersections.size()); } @Test @@ -199,8 +195,8 @@ void testVerySmallSegments() { List segments = List.of(newSegment(0.001, 0.001, 0.002, 0.002), newSegment(0.001, 0.002, 0.002, 0.001)); Set intersections = BentleyOttmann.findIntersections(segments); - assertEquals(1, intersections.size()); - assertTrue(containsPoint(intersections, 0.0015, 0.0015)); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 0.0015, 0.0015)); } @Test @@ -208,11 +204,11 @@ void testSegmentsShareCommonPoint() { List segmentsSameStart = List.of(newSegment(0, 0, 4, 4), newSegment(0, 0, 4, -4), newSegment(0, 0, -4, 4)); Set intersectionsSameStart = BentleyOttmann.findIntersections(segmentsSameStart); - assertTrue(containsPoint(intersectionsSameStart, 0.0, 0.0)); + Assertions.assertTrue(containsPoint(intersectionsSameStart, 0.0, 0.0)); List segmentsSameEnd = List.of(newSegment(0, 0, 4, 4), newSegment(8, 4, 4, 4), newSegment(4, 8, 4, 4)); Set intersectionsSameEnd = BentleyOttmann.findIntersections(segmentsSameEnd); - assertTrue(containsPoint(intersectionsSameEnd, 4.0, 4.0)); + Assertions.assertTrue(containsPoint(intersectionsSameEnd, 4.0, 4.0)); } @Test @@ -225,7 +221,7 @@ void testSegmentsAtAngles() { ); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(containsPoint(intersections, 2.0, 2.0)); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); } @Test @@ -249,10 +245,10 @@ void testPerformanceWithManySegments() { long duration = endTime - startTime; // Should complete in reasonable time (< 1 second for 100 segments) - assertTrue(duration < 1000, "Algorithm should complete in less than 1 second for 100 segments. Took: " + duration + "ms"); + Assertions.assertTrue(duration < 1000, "Algorithm should complete in less than 1 second for 100 segments. Took: " + duration + "ms"); // Just verify it returns a valid result - assertNotNull(intersections); + Assertions.assertNotNull(intersections); System.out.println("Performance test: 100 segments processed in " + duration + "ms, found " + intersections.size() + " intersections"); } @@ -267,8 +263,8 @@ void testIssueExample() { Set intersections = BentleyOttmann.findIntersections(segments); // Expected output: [(3, 3)] - assertEquals(1, intersections.size(), "Should find exactly one intersection"); - assertTrue(containsPoint(intersections, 3.0, 3.0), "Intersection should be at (3, 3)"); + Assertions.assertEquals(1, intersections.size(), "Should find exactly one intersection"); + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0), "Intersection should be at (3, 3)"); } @Test @@ -281,7 +277,7 @@ void testEventTypeOrdering() { ); Set intersections = BentleyOttmann.findIntersections(segments); - assertTrue(containsPoint(intersections, 2.0, 2.0)); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); } @Test @@ -291,8 +287,8 @@ void testCollinearOverlapWithInteriorPoint() { Set intersections = BentleyOttmann.findIntersections(segments); // Should find at least one overlap point (where segments touch/overlap) - assertFalse(intersections.isEmpty(), "Should find overlap points for collinear segments"); - assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain overlap boundary point"); + Assertions.assertFalse(intersections.isEmpty(), "Should find overlap points for collinear segments"); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain overlap boundary point"); } @Test @@ -302,8 +298,8 @@ void testCollinearTouchingAtBothEndpoints() { List segments = List.of(newSegment(0, 0, 4, 4), newSegment(4, 4, 8, 8)); Set intersections = BentleyOttmann.findIntersections(segments); - assertEquals(1, intersections.size()); - assertTrue(containsPoint(intersections, 4.0, 4.0), "Should find touching point"); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 4.0, 4.0), "Should find touching point"); } @Test @@ -314,9 +310,9 @@ void testCollinearOverlapPartialInterior() { Set intersections = BentleyOttmann.findIntersections(segments); // Should detect the overlap region - assertFalse(intersections.isEmpty()); + Assertions.assertFalse(intersections.isEmpty()); // The algorithm should return at least one of the boundary points - assertTrue(containsPoint(intersections, 3.0, 3.0) || containsPoint(intersections, 5.0, 5.0)); + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0) || containsPoint(intersections, 5.0, 5.0)); } private static BentleyOttmann.Segment newSegment(double x1, double y1, double x2, double y2) { From 42d8336d2ec1a5b1e3adebbbac29a9f46ef7d917 Mon Sep 17 00:00:00 2001 From: Indole Yi Date: Wed, 22 Oct 2025 08:18:07 +0800 Subject: [PATCH 8/8] Reorder import statements in BentleyOttmannTest --- .../java/com/thealgorithms/geometry/BentleyOttmannTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java index 008fdf8a7ce9..99240566a2f3 100644 --- a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -1,12 +1,11 @@ package com.thealgorithms.geometry; -import org.junit.jupiter.api.Assertions; - import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.Set; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /**