From 5f143d94adf51b75e69f9d6d37cbd4701a4c749c Mon Sep 17 00:00:00 2001 From: Piotr Idzik Date: Mon, 25 Mar 2024 19:31:44 +0000 Subject: [PATCH 1/4] tests: add tests checking if floodFill funtions throw when location is outside --- Recursive/test/FloodFill.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Recursive/test/FloodFill.test.js b/Recursive/test/FloodFill.test.js index 291788addd..618692dd97 100644 --- a/Recursive/test/FloodFill.test.js +++ b/Recursive/test/FloodFill.test.js @@ -21,6 +21,19 @@ describe('FloodFill', () => { }) }) +describe.each([breadthFirstSearch, depthFirstSearch])('%o', (floodFillFun) => { + it.each([ + [1, -1], + [-1, 1], + [0, 7], + [7, 0] + ])('throws for start position [%i, %i]', (location) => { + expect(() => + floodFillFun(generateTestRgbData(), location, green, orange) + ).toThrowError() + }) +}) + /** * Utility-function to test the function "breadthFirstSearch". * From 0e42fe02d89529f2626466c463cf5b986f8cc5cf Mon Sep 17 00:00:00 2001 From: Piotr Idzik Date: Mon, 25 Mar 2024 19:36:16 +0000 Subject: [PATCH 2/4] refactor: reduce code duplication by adding `checkLocation` to `FloodFill` --- Recursive/FloodFill.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Recursive/FloodFill.js b/Recursive/FloodFill.js index 33ea6025ad..bddc2c1880 100644 --- a/Recursive/FloodFill.js +++ b/Recursive/FloodFill.js @@ -20,6 +20,17 @@ const neighbors = [ [1, 1] ] +function checkLocation(rgbData, location) { + if ( + location[0] < 0 || + location[0] >= rgbData.length || + location[1] < 0 || + location[1] >= rgbData[0].length + ) { + throw new Error('location should point to a pixel within the rgbData') + } +} + /** * Implements the flood fill algorithm through a breadth-first approach using a queue. * @@ -34,14 +45,7 @@ export function breadthFirstSearch( targetColor, replacementColor ) { - if ( - location[0] < 0 || - location[0] >= rgbData.length || - location[1] < 0 || - location[1] >= rgbData[0].length - ) { - throw new Error('location should point to a pixel within the rgbData') - } + checkLocation(rgbData, location) const queue = [] queue.push(location) @@ -65,14 +69,7 @@ export function depthFirstSearch( targetColor, replacementColor ) { - if ( - location[0] < 0 || - location[0] >= rgbData.length || - location[1] < 0 || - location[1] >= rgbData[0].length - ) { - throw new Error('location should point to a pixel within the rgbData') - } + checkLocation(rgbData, location) depthFirstFill(rgbData, location, targetColor, replacementColor) } From cf34946a21423a5f8a1c44aeca1191acd9d62d72 Mon Sep 17 00:00:00 2001 From: Piotr Idzik Date: Tue, 26 Mar 2024 22:16:03 +0000 Subject: [PATCH 3/4] refactor: add and use `isInside` Co-authored-by: appgurueu <34514239+appgurueu@users.noreply.github.com> --- Recursive/FloodFill.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Recursive/FloodFill.js b/Recursive/FloodFill.js index bddc2c1880..9a6e5e8af8 100644 --- a/Recursive/FloodFill.js +++ b/Recursive/FloodFill.js @@ -20,13 +20,14 @@ const neighbors = [ [1, 1] ] +function isInside(rgbData, location) { + const x = location[0] + const y = location[1] + return x >= 0 && x < rgbData.length && y >= 0 && y < rgbData[0].length +} + function checkLocation(rgbData, location) { - if ( - location[0] < 0 || - location[0] >= rgbData.length || - location[1] < 0 || - location[1] >= rgbData[0].length - ) { + if (!isInside(rgbData, location)) { throw new Error('location should point to a pixel within the rgbData') } } @@ -119,10 +120,12 @@ function depthFirstFill(rgbData, location, targetColor, replacementColor) { rgbData[location[0]][location[1]] = replacementColor for (let i = 0; i < neighbors.length; i++) { - const x = location[0] + neighbors[i][0] - const y = location[1] + neighbors[i][1] - if (x >= 0 && x < rgbData.length && y >= 0 && y < rgbData[0].length) { - depthFirstFill(rgbData, [x, y], targetColor, replacementColor) + const newLocation = [ + location[0] + neighbors[i][0], + location[1] + neighbors[i][1] + ] + if (isInside(rgbData, newLocation)) { + depthFirstFill(rgbData, newLocation, targetColor, replacementColor) } } } From 1be1853dd012718ec3b139ea3d1c48e29312fcdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:50:59 +0100 Subject: [PATCH 4/4] Deduplicate further --- Recursive/FloodFill.js | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Recursive/FloodFill.js b/Recursive/FloodFill.js index 9a6e5e8af8..5143b8f4ff 100644 --- a/Recursive/FloodFill.js +++ b/Recursive/FloodFill.js @@ -9,7 +9,7 @@ * @see https://www.techiedelight.com/flood-fill-algorithm/ */ -const neighbors = [ +const neighborOffsets = [ [-1, -1], [-1, 0], [-1, 1], @@ -32,6 +32,15 @@ function checkLocation(rgbData, location) { } } +function* neighbors(rgbData, location) { + for (const offset of neighborOffsets) { + const neighborLocation = [location[0] + offset[0], location[1] + offset[1]] + if (isInside(rgbData, neighborLocation)) { + yield neighborLocation + } + } +} + /** * Implements the flood fill algorithm through a breadth-first approach using a queue. * @@ -96,13 +105,8 @@ function breadthFirstFill( if (rgbData[currentLocation[0]][currentLocation[1]] === targetColor) { rgbData[currentLocation[0]][currentLocation[1]] = replacementColor - - for (let i = 0; i < neighbors.length; i++) { - const x = currentLocation[0] + neighbors[i][0] - const y = currentLocation[1] + neighbors[i][1] - if (x >= 0 && x < rgbData.length && y >= 0 && y < rgbData[0].length) { - queue.push([x, y]) - } + for (const neighborLocation of neighbors(rgbData, currentLocation)) { + queue.push(neighborLocation) } } } @@ -118,15 +122,8 @@ function breadthFirstFill( function depthFirstFill(rgbData, location, targetColor, replacementColor) { if (rgbData[location[0]][location[1]] === targetColor) { rgbData[location[0]][location[1]] = replacementColor - - for (let i = 0; i < neighbors.length; i++) { - const newLocation = [ - location[0] + neighbors[i][0], - location[1] + neighbors[i][1] - ] - if (isInside(rgbData, newLocation)) { - depthFirstFill(rgbData, newLocation, targetColor, replacementColor) - } + for (const neighborLocation of neighbors(rgbData, location)) { + depthFirstFill(rgbData, neighborLocation, targetColor, replacementColor) } } }