diff --git a/src/main/java/com/thealgorithms/others/IterativeFloodFill.java b/src/main/java/com/thealgorithms/others/IterativeFloodFill.java new file mode 100644 index 000000000000..079ae364e066 --- /dev/null +++ b/src/main/java/com/thealgorithms/others/IterativeFloodFill.java @@ -0,0 +1,81 @@ +package com.thealgorithms.others; + +import java.util.LinkedList; +import java.util.Queue; + +public final class IterativeFloodFill { + private IterativeFloodFill() { + } + + /** + * Represents a point in 2D space with integer coordinates. + */ + private static class Point { + final int x; + final int y; + + Point(final int x, final int y) { + this.x = x; + this.y = y; + } + } + + /** + * Checks if a pixel should be skipped during flood fill operation. + * + * @param image The image to get boundaries + * @param x The x co-ordinate of pixel to check + * @param y The y co-ordinate of pixel to check + * @param oldColor The old color which is to be replaced in the image + * @return {@code true} if pixel should be skipped, else {@code false} + */ + private static boolean shouldSkipPixel(final int[][] image, final int x, final int y, final int oldColor) { + + if (x < 0 || x >= image.length || y < 0 || y >= image[0].length || image[x][y] != oldColor) { + return true; + } + + return false; + } + + /** + * Iteratively fill the 2D image with new color + * + * @param image The image to be filled + * @param x The x co-ordinate at which color is to be filled + * @param y The y co-ordinate at which color is to be filled + * @param newColor The new color which to be filled in the image + * @param oldColor The old color which is to be replaced in the image + * @see FloodFill BFS + */ + public static void floodFill(final int[][] image, final int x, final int y, final int newColor, final int oldColor) { + if (image.length == 0 || image[0].length == 0 || newColor == oldColor || shouldSkipPixel(image, x, y, oldColor)) { + return; + } + + Queue queue = new LinkedList<>(); + queue.add(new Point(x, y)); + + int[] dx = {0, 0, -1, 1, 1, -1, 1, -1}; + int[] dy = {-1, 1, 0, 0, -1, 1, 1, -1}; + + while (!queue.isEmpty()) { + Point currPoint = queue.poll(); + + if (shouldSkipPixel(image, currPoint.x, currPoint.y, oldColor)) { + continue; + } + + image[currPoint.x][currPoint.y] = newColor; + + for (int i = 0; i < 8; i++) { + int curX = currPoint.x + dx[i]; + int curY = currPoint.y + dy[i]; + + if (!shouldSkipPixel(image, curX, curY, oldColor)) { + queue.add(new Point(curX, curY)); + } + } + } + } +} diff --git a/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java b/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java new file mode 100644 index 000000000000..560f1df68e81 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java @@ -0,0 +1,163 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class IterativeFloodFillTest { + + @Test + void testForEmptyImage() { + int[][] image = {}; + int[][] expected = {}; + + IterativeFloodFill.floodFill(image, 4, 5, 3, 2); + assertArrayEquals(expected, image); + } + + @Test + void testForSingleElementImage() { + int[][] image = {{1}}; + int[][] expected = {{3}}; + + IterativeFloodFill.floodFill(image, 0, 0, 3, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForEmptyRow() { + int[][] image = {{}}; + int[][] expected = {{}}; + + IterativeFloodFill.floodFill(image, 4, 5, 3, 2); + assertArrayEquals(expected, image); + } + + @Test + void testForImageOne() { + int[][] image = { + {0, 0, 0, 0, 0, 0, 0}, + {0, 3, 3, 3, 3, 0, 0}, + {0, 3, 1, 1, 5, 0, 0}, + {0, 3, 1, 1, 5, 5, 3}, + {0, 3, 5, 5, 1, 1, 3}, + {0, 0, 0, 5, 1, 1, 3}, + {0, 0, 0, 3, 3, 3, 3}, + }; + + int[][] expected = { + {0, 0, 0, 0, 0, 0, 0}, + {0, 3, 3, 3, 3, 0, 0}, + {0, 3, 2, 2, 5, 0, 0}, + {0, 3, 2, 2, 5, 5, 3}, + {0, 3, 5, 5, 2, 2, 3}, + {0, 0, 0, 5, 2, 2, 3}, + {0, 0, 0, 3, 3, 3, 3}, + }; + + IterativeFloodFill.floodFill(image, 2, 2, 2, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForImageTwo() { + int[][] image = { + {0, 0, 1, 1, 0, 0, 0}, + {1, 1, 3, 3, 3, 0, 0}, + {1, 3, 1, 1, 5, 0, 0}, + {0, 3, 1, 1, 5, 5, 3}, + {0, 3, 5, 5, 1, 1, 3}, + {0, 0, 0, 5, 1, 1, 3}, + {0, 0, 0, 1, 3, 1, 3}, + }; + + int[][] expected = { + {0, 0, 2, 2, 0, 0, 0}, + {2, 2, 3, 3, 3, 0, 0}, + {2, 3, 2, 2, 5, 0, 0}, + {0, 3, 2, 2, 5, 5, 3}, + {0, 3, 5, 5, 2, 2, 3}, + {0, 0, 0, 5, 2, 2, 3}, + {0, 0, 0, 2, 3, 2, 3}, + }; + + IterativeFloodFill.floodFill(image, 2, 2, 2, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForImageThree() { + int[][] image = { + {1, 1, 2, 3, 1, 1, 1}, + {1, 0, 0, 1, 0, 0, 1}, + {1, 1, 1, 0, 3, 1, 2}, + }; + + int[][] expected = { + {4, 4, 2, 3, 4, 4, 4}, + {4, 0, 0, 4, 0, 0, 4}, + {4, 4, 4, 0, 3, 4, 2}, + }; + + IterativeFloodFill.floodFill(image, 0, 1, 4, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForSameNewAndOldColor() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 0, 1, 1, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForBigImage() { + int[][] image = new int[100][100]; + + assertDoesNotThrow(() -> IterativeFloodFill.floodFill(image, 0, 0, 1, 0)); + } + + @Test + void testForBelowZeroX() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, -1, 1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForBelowZeroY() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 1, -1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForAboveBoundaryX() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 100, 1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForAboveBoundaryY() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 1, 100, 1, 0); + assertArrayEquals(expected, image); + } +}