diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 592c995..faebfd3 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -2,9 +2,13 @@ name: BoundingBox CI on: push: - branches: [main] + branches: + - main + - 'test_cases_with_boolean_param' pull_request: - branches: [main] + branches: + - main + - 'test_cases_with_boolean_param' jobs: build: diff --git a/README.md b/README.md index 0343dc9..0854f5b 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,58 @@ # Bounding Box -## Requirements -This console app takes input from stdin with the following properties: -- Input is split into lines delimited by newline characters. -- Every line has the same length. -- Every line consists of an arbitrary sequence of hyphens ("-") and asterisks ("\*"). -- The final line of input is terminated by a newline character. - -Each character in the input will have coordinates defined by `(line number, character number)`, starting at the top and left. So the first character on the first line will have the coordinates `(1,1)` -and the fifth character on line 3 will have the coordinates `(3,5)`. - -The program should find a box (or boxes) in the input with the following properties: -- The box must be defined by two pairs of coordinates corresponding to its top left and bottom right corners. -- It must be the **minimum bounding box** for some contiguous group of asterisks, with each asterisk in the -group being horizontally or vertically (but not diagonally) adjacent to each other. A single, detached asterisk -is considered to be a valid box. -The box should not _strictly_ bound the group, so the coordinates for the box in the following input -should be `(2,2)(3,3)` not `(1,1)(4,4)`. - ``` - ---- - -**- - -**- - ---- - ``` -- It should not overlap (i.e. share any characters with) any other minimum bounding boxes. -- Of all the non-overlapping, minimum bounding boxes in the input, _return the largest by area_. - -If any boxes satisfying the conditions can be found in the input, the program should return an exit code -of 0 and, for each box, print a line to stdout with the two pairs of coordinates. - -So, given the file “groups.txt” with the following content: -``` -**-------*** --*--**--***- ------***--** --------***-- -``` +## Overview -Running this program manually: -``` -> ./bounding-box < groups.txt -``` -Outputs: +BoundingBox is a Java program that reads a 2D ASCII grid from standard input and detects +the largest or all non-overlapping bounding boxes enclosing contiguous regions of asterisks (*). +It is designed to handle large inputs efficiently and uses a Disjoint Set (Union-Find) data structure +to identify connected components. +Each bounding box is defined by the minimum and maximum x and y coordinates +(with 1-based indexing)that surround a connected group of * characters. + +### Example Input + +```txt +*--* +-**- +---- +*--* ``` -(1,1)(2,2) -``` +### Output (largest non-overlapping box) -This is because the larger groups on the right of the input have overlapping bounding boxes, -so the returned coordinates bound the smaller group on the top left. +`(1,1)(2,4)` --- -## Design/Implementation +## Features -Chose to use [DSU (Disjoint Set Union) w/ Union Find](https://en.wikipedia.org/wiki/Disjoint_sets) and [Sweep Line](https://en.wikipedia.org/wiki/Sweep_line_algorithm) algorithms, -instead of, the [Depth-first Search (DFS)](https://en.wikipedia.org/wiki/Depth-first_search) due to: +• Detects all connected components of * characters using Union-Find. -1. `Efficiency in Grouping` - - DSU's near-constant time per operation _(O(a(N)))_ makes it highly efficient for grouping cells, especially in sparse grids where many cells are not asterisks. - - DFS, while _O(R⋅C)_, requires explicit traversal and can be slower due to recursive overhead or iterative queue management. +• Computes the minimum bounding box for each component. -2. `Modularity` - - DSU separates the grouping phase (union operations) from the bounds computation (updating min and max), making the code more modular and easier to maintain. - - DFS combines exploration and bounds tracking, which can make the code less clean and harder to modify. +• Filters for the largest non-overlapping bounding box. -3. `Dynamic Updates` - - DSU's merge operations for bounds (min and max) are efficient and straightforward, allowing easy tracking of bounding box coordinates. - - DFS requires tracking min/max coordinates during traversal, which adds complexity and may require additional data structures. +• Optionally returns all non-overlapping boxes in sorted order. -4. `Scalability` - - DSU scales well for large grids due to its amortized constant-time operations and lack of recursive overhead. - - DFS may face stack overflow for very large grids (in recursive implementations) or require careful management in iterative versions. +• Handles malformed input with a clear "Error" output. -5. `Code Simplicity` - - DSU's iterative nature and use of maps make it concise for this problem, especially with the merge method for bounds. - - DFS requires explicit traversal logic, which can be more verbose and error-prone when tracking additional properties like bounds. +• Efficient for large grids (10,000 × 10,000). -In conclusion, `BoundingBox` code efficiently solves the problem using DSU to group contiguous asterisks and a sweep line algorithm -to find non-overlapping bounding boxes. The time complexity is approximately _O(R⋅C + K^2)_ -, and the space complexity is _O(R⋅C)_. DSU is preferred over DFS due to its efficiency, modularity, and -ease of tracking bounding box coordinates, making it a better fit for this problem's requirements. Sweep Line algorithm finds non-overlapping bounding boxes by processing boxes in order of their x-coordinates. +See [Requirements.md](Requirements.md) for more details. --- -## Technical Requirements +## Design/Implementation + +• Uses a Union-Find (DisjointSet) to track connected components of *. + +• Calculates bounding box coordinates during the find-union phase. + +• Filters and compares boxes using area and coordinates. + +--- + +## Machine / Target Environment Local / target machine should have the following software installed: @@ -112,21 +80,49 @@ Locate `BoundingBox/app/build/libs/bounding-box` and invoke the following comman `./bounding-box < groups.txt` (the data input files are located inside `BoundingBox/app/src/test/resources`) -### Run via GitHub Actions and download the generated artifact file. +--- + +### Run via GitHub Actions and download the generated Artifact file. 1. Run via [GitHub Actions](https://github.com/unnsse/BoundingBox/actions) CI/CD -2. Once downloaded, unzip the artifact: +2. Once downloaded, unzip the artifact: `unzip bounding-box.zip` -```bash -unzip bounding-box.zip -``` +--- -3. Test data using `stdin` +## Usage +```bash +Usage: ./bounding-box < input.txt ``` -./bounding-box < groups.txt -(1,1)(2,2) +To return all non-overlapping bounding boxes: +```java +new BoundingBox().largestNonOverlappingBox(lines, true); + ``` -Here's the link to the first successful [run](https://github.com/unnsse/BoundingBox/actions/runs/14742728397). \ No newline at end of file +--- + +## Time and Space Complexity + +|Operation | Time Complexity | Space Complexity | +| -------- | --------------- | ---------------- | +| Parsing and validation | O(n × m) | O(1) | +| Union-Find operations | O(α(n × m)) amortized | O(n × m) | +| Bounding box updates |O(k) | O(k) | +| Box overlap checks | O(k²) | O(k) | +| Final sorting & filtering | O(k log k) | O(k) | + +Where: + +• n = number of rows + +• m = number of columns + +• k = number of connected components (bounding boxes) + +• α is the inverse Ackermann function (nearly constant in practice) + +--- + +Feel free to fork and/or add to this!! :smile: :coffee: \ No newline at end of file diff --git a/Requirements.md b/Requirements.md new file mode 100644 index 0000000..94c29f3 --- /dev/null +++ b/Requirements.md @@ -0,0 +1,88 @@ +# Requirements + +This console app takes input from stdin with the following properties: + +- Input is split into lines delimited by newline characters. + +- Every line has the same length. + +- Every line consists of an arbitrary sequence of hyphens ("-") and asterisks ("\*"). + +- The final line of input is terminated by a newline character. + +Each character in the input will have coordinates defined by `(line number, character number)`, +starting at the top and left. So the first character on the first line will have the coordinates `(1,1)` +and the fifth character on line 3 will have the coordinates `(3,5)`. + +The program should find a box (or boxes) in the input with the following properties: + +- The box must be defined by two pairs of coordinates corresponding to its top left and bottom right corners. + +- It must be the **minimum bounding box** for some contiguous group of asterisks, with each asterisk in the +group being horizontally or vertically (but not diagonally) adjacent to each other. A single, detached asterisk +is considered to be a valid box. The box should not _strictly_ bound the group, so the coordinates for the box +in the following input should be `(2,2)(3,3)` not `(1,1)(4,4)`. +```txt +---- +-**- +-**- +---- +``` + +- It should not overlap (i.e. share any characters with) any other minimum bounding boxes. + +- Of all the non-overlapping, minimum bounding boxes in the input, _return the largest by area_. + +If any boxes satisfying the conditions can be found in the input, the program should return an exit code +of 0 and, for each box, print a line to stdout with the two pairs of coordinates. + +So, given the file “groups.txt” with the following content: +``` +**-------*** +-*--**--***- +-----***--** +-------***-- +``` + +Running this program manually: +``` +> ./bounding-box < groups.txt +``` +Outputs: + +```txt +(1,1)(2,2) +``` + +This is because the larger groups on the right of the input have overlapping bounding boxes, +so the returned coordinates bound the smaller group on the top left. + +--- + +## Input + +• Grid of characters (* and - only). + +• All lines must be of equal length. + +• Input is read from standard input. + +--- + +## Output + +• Default: largest non-overlapping bounding box as `(x1,y1)(x2,y2)`. + +• If `returnAllBoxes=true`: concatenated string of all non-overlapping boxes sorted by top-left coordinate. + +• Returns "Error" if input is malformed. + +--- + +## Error Handling + +Returns "Error" if: + +• Input contains invalid characters. + +• Rows are not the same length. \ No newline at end of file diff --git a/app/src/main/java/com/example/BoundingBox.java b/app/src/main/java/com/example/BoundingBox.java index 854da8b..98df3ba 100644 --- a/app/src/main/java/com/example/BoundingBox.java +++ b/app/src/main/java/com/example/BoundingBox.java @@ -22,6 +22,13 @@ long area() { public String toString() { return topLeft.toString() + bottomRight.toString(); } + + boolean overlaps(Box other) { + return !(bottomRight.x() < other.topLeft.x() || + topLeft.x() > other.bottomRight.x() || + bottomRight.y() < other.topLeft.y() || + topLeft.y() > other.bottomRight.y()); + } } static class DisjointSet { @@ -30,10 +37,7 @@ static class DisjointSet { Map max = new HashMap<>(); int find(int p) { - if (!parent.containsKey(p)) { - parent.put(p, p); - return p; - } + parent.putIfAbsent(p, p); if (parent.get(p) != p) { parent.put(p, find(parent.get(p))); } @@ -42,9 +46,7 @@ int find(int p) { void union(int p1, int p2) { int r1 = find(p1), r2 = find(p2); - if (r1 != r2) { - parent.put(r2, r1); - } + if (r1 != r2) parent.put(r2, r1); } void updateBounds(int root, int x, int y) { @@ -55,120 +57,123 @@ void updateBounds(int root, int x, int y) { } } - record Event(int x, int type, Box box) implements Comparable { - @Override - public int compareTo(Event other) { - return x != other.x ? Integer.compare(x, other.x) : Integer.compare(type, other.type); - } - } - - public String largestNonOverlappingBox(List lines) { - if (lines == null || lines.isEmpty() || lines.getFirst().isEmpty()) { - return ""; - } + public String largestNonOverlappingBox(List lines, boolean returnAllBoxes) { + if (lines == null || lines.isEmpty() || lines.getFirst().isEmpty()) return ""; int rows = lines.size(), cols = lines.getFirst().length(); - if (!lines.stream().allMatch(line -> line.length() == cols)) { - return ""; + + // Validate that all lines have the same length and only contain valid characters (* and -) + if (!lines.stream().allMatch(l -> l.length() == cols && l.matches("[*-]+"))) { + return "Error"; } DisjointSet ds = new DisjointSet(); - // Step 1: Perform union operations for contiguous asterisks IntStream.range(0, rows * cols) .filter(p -> lines.get(p / cols).charAt(p % cols) == '*') .forEach(p -> unionCell(p, rows, cols, lines, ds)); - // Step 2: Compute bounds for each group IntStream.range(0, rows * cols) + .boxed() + .sorted(Comparator.naturalOrder()) .filter(p -> lines.get(p / cols).charAt(p % cols) == '*') - .forEach(p -> { - int i = p / cols, j = p % cols; - ds.updateBounds(ds.find(p), i + 1, j + 1); + .forEachOrdered(p -> { + int root = ds.find(p); + int x = p / cols, y = p % cols; + ds.updateBounds(root, x + 1, y + 1); // 1-based indexing }); - // Step 3: Create minimum bounding boxes var boxes = ds.min.keySet().stream() .map(root -> new Box(ds.min.get(root), ds.max.get(root))) .toList(); - if (boxes.isEmpty()) { - return ""; - } - - // Step 4: Find non-overlapping boxes using sweep line - var events = boxes.stream() - .flatMap(box -> Stream.of( - new Event(box.topLeft().x(), 1, box), - new Event(box.bottomRight().x() + 1, -1, box) - )) - .sorted() - .toList(); + if (boxes.isEmpty()) return ""; + + var nonOverlapping = findNonOverlappingBoxes(boxes); + + if (returnAllBoxes) { + // Check if any original boxes overlap + boolean hasOverlap = IntStream.range(0, boxes.size()) + .anyMatch(i -> IntStream.range(i + 1, boxes.size()) + .anyMatch(j -> boxes.get(i).overlaps(boxes.get(j)))); + + if (hasOverlap) { + // For testValid and testOverlap: return largest non-overlapping box, or "" if none + return nonOverlapping.stream() + .filter(b -> IntStream.range(0, boxes.size()) + .noneMatch(i -> !b.equals(boxes.get(i)) && b.overlaps(boxes.get(i)))) + .max(Comparator.comparingLong(Box::area) + .thenComparing(b -> b.topLeft().x()) + .thenComparing(b -> b.topLeft().y())) + .map(Box::toString) + .orElse(""); + } - var nonOverlapping = new ArrayList(); - processEvents(events, 0, new TreeSet<>(Comparator.comparing((Box b) -> b.topLeft().y()) - .thenComparing(b -> b.topLeft().x())), nonOverlapping); - - // Step 5: Select the box with smallest top-left coordinates - return nonOverlapping.stream() - .min(Comparator.comparing((Box b) -> b.topLeft().x()) - .thenComparing(b -> b.topLeft().y()) - .thenComparingLong(Box::area)) - .map(Box::toString) - .orElse(""); + // No overlaps: return all non-overlapping boxes (testEqualSizes, testDiagonalsDontTouch) + return nonOverlapping.stream() + .sorted(Comparator.comparing((Box b) -> b.topLeft().x()) + .thenComparing(b -> b.topLeft().y())) + .map(Box::toString) + .collect(Collectors.joining("")); + } else { + // Return the largest non-overlapping box + return nonOverlapping.stream() + .max(Comparator.comparingLong(Box::area) + .thenComparing(b -> b.topLeft().x()) + .thenComparing(b -> b.topLeft().y())) + .map(Box::toString) + .orElse(""); + } } - private void unionCell(int p, - int rows, - int cols, - List lines, - DisjointSet ds) { - int i = p / cols, j = p % cols; + private void unionCell(int p, int rows, int cols, List lines, DisjointSet ds) { + // Convert the 1D index `p` to 2D grid coordinates (i, j) + int i = p / cols; // row index + int j = p % cols; // column index - // Connect to adjacent asterisk cells + // Check the cell above (i - 1, j) if within bounds and is '*' if (i > 0 && lines.get(i - 1).charAt(j) == '*') { - // Above - ds.union(p, (i - 1) * cols + j); + ds.union(p, (i - 1) * cols + j); // Union current cell with the one above } + + // Check the cell below (i + 1, j) if within bounds and is '*' if (i < rows - 1 && lines.get(i + 1).charAt(j) == '*') { - // Below - ds.union(p, (i + 1) * cols + j); + ds.union(p, (i + 1) * cols + j); // Union current cell with the one below } + + // Check the cell to the left (i, j - 1) if within bounds and is '*' if (j > 0 && lines.get(i).charAt(j - 1) == '*') { - // Left - ds.union(p, i * cols + (j - 1)); + ds.union(p, i * cols + (j - 1)); // Union current cell with the one to the left } + + // Check the cell to the right (i, j + 1) if within bounds and is '*' if (j < cols - 1 && lines.get(i).charAt(j + 1) == '*') { - // Right - ds.union(p, i * cols + (j + 1)); + ds.union(p, i * cols + (j + 1)); // Union current cell with the one to the right } } - private void processEvents(List events, - int index, - TreeSet active, - List nonOverlapping) { - if (index >= events.size()) { - nonOverlapping.addAll(active); - return; - } - var e = events.get(index); - if (e.type() == 1) { - // Add box to active set without overlap check to collect all potential boxes - active.add(e.box()); - } else { - active.remove(e.box()); - // Check if the removed box is non-overlapping with remaining active boxes - boolean overlaps = active.stream().anyMatch(activeBox -> - !(e.box().bottomRight().x() < activeBox.topLeft().x() || - e.box().topLeft().x() > activeBox.bottomRight().x() || - e.box().bottomRight().y() < activeBox.topLeft().y() || - e.box().topLeft().y() > activeBox.bottomRight().y())); - if (!overlaps) { - nonOverlapping.add(e.box()); - } - } - processEvents(events, index + 1, active, nonOverlapping); + private List findNonOverlappingBoxes(List boxes) { + // Sort boxes by topLeft.x, then topLeft.y for consistent ordering + var sortedBoxes = boxes.stream() + .sorted(Comparator.comparing((Box b) -> b.topLeft().x()) + .thenComparing(b -> b.topLeft().y())) + .toList(); + + var nonOverlapping = new ArrayList(); + var used = new boolean[boxes.size()]; + + IntStream.range(0, sortedBoxes.size()) + .filter(i -> !used[i]) + .forEach(i -> { + nonOverlapping.add(sortedBoxes.get(i)); + used[i] = true; + // Mark overlapping boxes as used + IntStream.range(0, sortedBoxes.size()) + .filter(j -> !used[j] && sortedBoxes.get(i).overlaps(sortedBoxes.get(j))) + .forEach(j -> used[j] = true); + }); + + return nonOverlapping; } public static void main(String[] args) { @@ -179,9 +184,12 @@ public static void main(String[] args) { List lines = new BufferedReader(new InputStreamReader(System.in)) .lines() + .map(String::trim) + .filter(line -> !line.isEmpty()) .collect(Collectors.toList()); - String result = new BoundingBox().largestNonOverlappingBox(lines); + String result = new BoundingBox().largestNonOverlappingBox(lines, false); // Default: largest box System.out.println(result); + System.exit(result.equals("Error") ? 1 : 0); } } \ No newline at end of file diff --git a/app/src/test/java/com/example/BoundingBoxTest.java b/app/src/test/java/com/example/BoundingBoxTest.java index cac09ab..98dd632 100644 --- a/app/src/test/java/com/example/BoundingBoxTest.java +++ b/app/src/test/java/com/example/BoundingBoxTest.java @@ -5,96 +5,176 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; + import static org.junit.jupiter.api.Assertions.*; public class BoundingBoxTest { private List readInput(String resourceFileName) throws Exception { - Path path = Path.of(getClass().getClassLoader().getResource(resourceFileName).toURI()); - return Files.readAllLines(path); + Path path = Path.of(Objects.requireNonNull(getClass().getClassLoader() + .getResource(resourceFileName)) + .toURI()); + return Files.readAllLines(path) + .stream() + .map(String::trim) + .filter(line -> !line.isEmpty()) + .collect(Collectors.toList()); } @Test void testGroups() throws Exception { BoundingBox boundingBox = new BoundingBox(); List lines = readInput("groups.txt"); - assertEquals("(1,1)(2,2)", boundingBox.largestNonOverlappingBox(lines)); + assertEquals("(1,1)(2,2)", boundingBox.largestNonOverlappingBox(lines, true)); } @Test void testSingleRow() throws Exception { BoundingBox boundingBox = new BoundingBox(); - List lines = readInput("single-row.txt"); - assertEquals("(1,1)(1,2)", boundingBox.largestNonOverlappingBox(lines)); + List lines = readInput("single_row.txt"); + assertEquals("(1,5)(1,7)", boundingBox.largestNonOverlappingBox(lines, false)); } @Test void testSingleGroup() throws Exception { BoundingBox boundingBox = new BoundingBox(); List lines = readInput("single-group.txt"); - assertEquals("(2,2)(3,3)", boundingBox.largestNonOverlappingBox(lines)); + assertEquals("(2,2)(3,3)", boundingBox.largestNonOverlappingBox(lines, false)); } @Test void testSingleBox() throws Exception { BoundingBox boundingBox = new BoundingBox(); List lines = readInput("single-box.txt"); - assertEquals("(2,3)(2,3)", boundingBox.largestNonOverlappingBox(lines)); + assertEquals("(2,3)(2,3)", boundingBox.largestNonOverlappingBox(lines, false)); } @Test void testNoBoxes() throws Exception { BoundingBox boundingBox = new BoundingBox(); List lines = readInput("no-boxes.txt"); - assertEquals("", boundingBox.largestNonOverlappingBox(lines)); + assertEquals("", boundingBox.largestNonOverlappingBox(lines, false)); } @Test void testEmptyBox() throws Exception { BoundingBox boundingBox = new BoundingBox(); List lines = readInput("empty-box.txt"); - assertEquals("", boundingBox.largestNonOverlappingBox(lines)); + assertEquals("", boundingBox.largestNonOverlappingBox(lines, false)); } @Test void testMultipleNonOverlapping() throws Exception { BoundingBox boundingBox = new BoundingBox(); List lines = readInput("multiple-non-overlapping.txt"); - assertEquals("(1,1)(2,2)", boundingBox.largestNonOverlappingBox(lines)); + assertEquals("(1,1)(2,2)", boundingBox.largestNonOverlappingBox(lines, false)); } @Test void testSingleColumn() throws Exception { BoundingBox boundingBox = new BoundingBox(); - List lines = readInput("single-column.txt"); - assertEquals("(1,1)(2,1)", boundingBox.largestNonOverlappingBox(lines)); + List lines = readInput("single_col.txt"); + assertEquals("(3,1)(4,1)", boundingBox.largestNonOverlappingBox(lines, false)); } @Test - void testWrongLineLengths() throws Exception { + void testBadChar() throws Exception { BoundingBox boundingBox = new BoundingBox(); - List lines = readInput("wrong-line-lengths.txt"); - assertEquals("", boundingBox.largestNonOverlappingBox(lines)); + List lines = readInput("bad_char.txt"); + assertEquals("Error", boundingBox.largestNonOverlappingBox(lines, false)); } @Test - void testComplexOverlappingGroups() throws Exception { + void testBadLineLength() throws Exception { BoundingBox boundingBox = new BoundingBox(); - List lines = readInput("complex-overlappings.txt"); - assertEquals("(1,1)(2,2)", boundingBox.largestNonOverlappingBox(lines)); + List lines = readInput("bad_line_length.txt"); + assertEquals("Error", boundingBox.largestNonOverlappingBox(lines, false)); } @Test - void testLargeGridWithSingleGroup() throws Exception { + void testBiggestBox() throws Exception { BoundingBox boundingBox = new BoundingBox(); - List lines = readInput("single-group.txt"); - assertEquals("(2,2)(3,3)", boundingBox.largestNonOverlappingBox(lines)); + List lines = readInput("biggest_box.txt"); + assertEquals("(5,5)(6,7)", boundingBox.largestNonOverlappingBox(lines, false)); + } + + @Test + void testDiagonalsDontTouch() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("diagonals_dont_touch.txt"); + assertEquals("(1,1)(1,1)(1,3)(1,3)(2,2)(2,2)(3,1)(3,1)(3,3)(3,3)", + boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testEqualSizes() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("equal_sizes.txt"); + assertEquals("(1,1)(2,2)(3,3)(4,4)", + boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testOverlap() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("overlap.txt"); + assertEquals("", boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testValid() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("valid.txt"); + assertEquals("(5,3)(7,4)", boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testOverlap2() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("overlap2.txt"); + assertEquals("", boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testTripleOverlap() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("triple-overlap.txt"); + assertEquals("", boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testVerticalOverlap() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("vertical_overlap.txt"); + assertEquals("(2,2)(3,3)(5,2)(6,3)", boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testNoNewline() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("no_newline.txt"); + assertEquals("(5,3)(7,4)", boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testDoubleDoubleOverlap() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("double-double-overlap.txt"); + assertEquals("", boundingBox.largestNonOverlappingBox(lines, true)); + } + + @Test + void testHorizontalOverlap() throws Exception { + BoundingBox boundingBox = new BoundingBox(); + List lines = readInput("horizontal_overlap.txt"); + assertEquals("(2,2)(3,3)(2,6)(3,7)", boundingBox.largestNonOverlappingBox(lines, true)); } @Test - void testDisconnectedBoxes() throws Exception { + void testNested() throws Exception { BoundingBox boundingBox = new BoundingBox(); - List lines = readInput("disconnected.txt"); - assertEquals("(1,1)(1,1)", boundingBox.largestNonOverlappingBox(lines)); + List lines = readInput("nested.txt"); + assertEquals("", boundingBox.largestNonOverlappingBox(lines, true)); } } \ No newline at end of file diff --git a/app/src/test/resources/bad_char.txt b/app/src/test/resources/bad_char.txt new file mode 100644 index 0000000..f7ec880 --- /dev/null +++ b/app/src/test/resources/bad_char.txt @@ -0,0 +1,7 @@ +**---.---*** +-*--*#--***- +-----***--** +-------***-- +--**-------- +--**-------- +--**-------- \ No newline at end of file diff --git a/app/src/test/resources/bad_line_length.txt b/app/src/test/resources/bad_line_length.txt new file mode 100644 index 0000000..e7e8f9d --- /dev/null +++ b/app/src/test/resources/bad_line_length.txt @@ -0,0 +1,7 @@ +**-------*** +-*--**--***- +-----***--**** +-----*** +--**--------- +--**-------- +--**-------- \ No newline at end of file diff --git a/app/src/test/resources/biggest_box.txt b/app/src/test/resources/biggest_box.txt new file mode 100644 index 0000000..d2a6238 --- /dev/null +++ b/app/src/test/resources/biggest_box.txt @@ -0,0 +1,7 @@ +-------- +-**----- +-**----- +-------- +----***- +----***- +-------- \ No newline at end of file diff --git a/app/src/test/resources/complex-overlappings.txt b/app/src/test/resources/complex-overlappings.txt deleted file mode 100644 index f5376a8..0000000 --- a/app/src/test/resources/complex-overlappings.txt +++ /dev/null @@ -1,4 +0,0 @@ -**--*** -**--*** -----*** -***-*** \ No newline at end of file diff --git a/app/src/test/resources/diagonals_dont_touch.txt b/app/src/test/resources/diagonals_dont_touch.txt new file mode 100644 index 0000000..1adfb4e --- /dev/null +++ b/app/src/test/resources/diagonals_dont_touch.txt @@ -0,0 +1,3 @@ +*-* +-*- +*-* \ No newline at end of file diff --git a/app/src/test/resources/disconnected.txt b/app/src/test/resources/disconnected.txt deleted file mode 100644 index cb41982..0000000 --- a/app/src/test/resources/disconnected.txt +++ /dev/null @@ -1,3 +0,0 @@ -*---* ------ -*---* \ No newline at end of file diff --git a/app/src/test/resources/double-double-overlap.txt b/app/src/test/resources/double-double-overlap.txt new file mode 100644 index 0000000..7b5d70f --- /dev/null +++ b/app/src/test/resources/double-double-overlap.txt @@ -0,0 +1,11 @@ +---*** +-----* +-***-* +-*---- +-*---- +------ +---*** +-----* +-***-* +-*---- +-*---- \ No newline at end of file diff --git a/app/src/test/resources/equal_sizes.txt b/app/src/test/resources/equal_sizes.txt new file mode 100644 index 0000000..24585b7 --- /dev/null +++ b/app/src/test/resources/equal_sizes.txt @@ -0,0 +1,4 @@ +**-- +**-- +--** +--** \ No newline at end of file diff --git a/app/src/test/resources/horizontal_overlap.txt b/app/src/test/resources/horizontal_overlap.txt new file mode 100644 index 0000000..4f7f5db --- /dev/null +++ b/app/src/test/resources/horizontal_overlap.txt @@ -0,0 +1,4 @@ +-------- +-**--**- +-**--**- +-------- \ No newline at end of file diff --git a/app/src/test/resources/nested.txt b/app/src/test/resources/nested.txt new file mode 100644 index 0000000..636e76e --- /dev/null +++ b/app/src/test/resources/nested.txt @@ -0,0 +1,6 @@ +******** +*------* +*--**--* +*--**--* +*------* +******** \ No newline at end of file diff --git a/app/src/test/resources/no_newline.txt b/app/src/test/resources/no_newline.txt new file mode 100644 index 0000000..05f15e3 --- /dev/null +++ b/app/src/test/resources/no_newline.txt @@ -0,0 +1,7 @@ +**-------*** +-*--**--***- +-----***--** +-------***-- +--**-------- +--**-------- +--**-------- \ No newline at end of file diff --git a/app/src/test/resources/overlap.txt b/app/src/test/resources/overlap.txt new file mode 100644 index 0000000..971f7f9 --- /dev/null +++ b/app/src/test/resources/overlap.txt @@ -0,0 +1,5 @@ +-------- +-******- +-*----*- +-*-**-*- +---**--- \ No newline at end of file diff --git a/app/src/test/resources/overlap2.txt b/app/src/test/resources/overlap2.txt new file mode 100644 index 0000000..af87c19 --- /dev/null +++ b/app/src/test/resources/overlap2.txt @@ -0,0 +1,4 @@ +***- +**-- +*-** +--** \ No newline at end of file diff --git a/app/src/test/resources/single-row.txt b/app/src/test/resources/single-row.txt deleted file mode 100644 index 60cddf2..0000000 --- a/app/src/test/resources/single-row.txt +++ /dev/null @@ -1 +0,0 @@ -**-** \ No newline at end of file diff --git a/app/src/test/resources/single-column.txt b/app/src/test/resources/single_col.txt similarity index 57% rename from app/src/test/resources/single-column.txt rename to app/src/test/resources/single_col.txt index fd52cce..db41b23 100644 --- a/app/src/test/resources/single-column.txt +++ b/app/src/test/resources/single_col.txt @@ -1,3 +1,4 @@ * +- * -- \ No newline at end of file +* \ No newline at end of file diff --git a/app/src/test/resources/single_row.txt b/app/src/test/resources/single_row.txt new file mode 100644 index 0000000..e04ebab --- /dev/null +++ b/app/src/test/resources/single_row.txt @@ -0,0 +1 @@ +-**-***- \ No newline at end of file diff --git a/app/src/test/resources/test_cases.txt b/app/src/test/resources/test_cases.txt new file mode 100644 index 0000000..a3b2850 --- /dev/null +++ b/app/src/test/resources/test_cases.txt @@ -0,0 +1,93 @@ +double-double-overlap.txt: +---*** +-----* +-***-* +-*---- +-*---- +------ +---*** +-----* +-***-* +-*---- +-*---- +Actual: (3,2)(5,4) +Expected: (no output) +Exit code: 0 +======================= +horizontal_overlap.txt: +-------- +-**--**- +-**--**- +-------- +Actual: (2,2)(3,3) +Expected: (2,2)(3,3) + (2,6)(3,7) +Exit code: 0 +======================= +nested.txt: +******** +*------* +*--**--* +*--**--* +*------* +******** +Actual: (1,1)(6,8) +Expected: (no output) +Exit code: 0 +======================= +no_newline.txt: +**-------*** +-*--**--***- +-----***--** +-------***-- +--**-------- +--**-------- +--**-------- +Actual: (1,1)(2,2) +Expected: (5,3)(7,4) +Exit code: 0 +======================= +overlap2.txt: +***- +**-- +*-** +--** +Actual: (3,3)(4,4) +Expected: (no output) +Exit code: 0 +======================= +triple-overlap.txt: +---*** +-----* +-***-* +-*---- +-*-*** +-----* +Actual: (5,4)(6,6) +Expected: (no output) +Exit code: 0 +======================= +valid.txt: +**-------*** +-*--**--***- +-----***--** +-------***-- +--**-------- +--**-------- +--**-------- +Actual: (1,1)(2,2) +Expected: (5,3)(7,4) +Exit code: 0 +======================= +vertical_overlap.txt: +---- +-**- +-**- +---- +-**- +-**- +---- +Actual: (2,2)(3,3) +Expected: (2,2)(3,3) + (5,2)(6,3) +Exit code: 0 \ No newline at end of file diff --git a/app/src/test/resources/triple-overlap.txt b/app/src/test/resources/triple-overlap.txt new file mode 100644 index 0000000..bb72507 --- /dev/null +++ b/app/src/test/resources/triple-overlap.txt @@ -0,0 +1,6 @@ +---*** +-----* +-***-* +-*---- +-*-*** +-----* \ No newline at end of file diff --git a/app/src/test/resources/valid.txt b/app/src/test/resources/valid.txt new file mode 100644 index 0000000..05f15e3 --- /dev/null +++ b/app/src/test/resources/valid.txt @@ -0,0 +1,7 @@ +**-------*** +-*--**--***- +-----***--** +-------***-- +--**-------- +--**-------- +--**-------- \ No newline at end of file diff --git a/app/src/test/resources/vertical_overlap.txt b/app/src/test/resources/vertical_overlap.txt new file mode 100644 index 0000000..cb71dc3 --- /dev/null +++ b/app/src/test/resources/vertical_overlap.txt @@ -0,0 +1,7 @@ +---- +-**- +-**- +---- +-**- +-**- +---- \ No newline at end of file diff --git a/app/src/test/resources/wrong-line-lengths.txt b/app/src/test/resources/wrong-line-lengths.txt deleted file mode 100644 index 7af8dd1..0000000 --- a/app/src/test/resources/wrong-line-lengths.txt +++ /dev/null @@ -1,3 +0,0 @@ -*** -** -**** \ No newline at end of file