From 84c8fff7cbf4dede7aa4ba88bae6d62f94bf3293 Mon Sep 17 00:00:00 2001 From: Ishaan Verma Date: Sat, 19 Sep 2020 19:00:51 +0530 Subject: [PATCH 1/7] add floodfill in python --- contents/flood_fill/code/python/flood_fill.py | 105 ++++++++++++++++++ contents/flood_fill/flood_fill.md | 12 ++ 2 files changed, 117 insertions(+) create mode 100644 contents/flood_fill/code/python/flood_fill.py diff --git a/contents/flood_fill/code/python/flood_fill.py b/contents/flood_fill/code/python/flood_fill.py new file mode 100644 index 000000000..f235d9bfd --- /dev/null +++ b/contents/flood_fill/code/python/flood_fill.py @@ -0,0 +1,105 @@ +from collections import namedtuple +from queue import Queue + +Point = namedtuple("Point", "x y") +Canvas = namedtuple("Canvas", "max_x max_y data") + +def inbounds(canvas, p): + if p.x < 0 or p.y < 0 or p.x >= canvas.max_x or p.y >= canvas.max_y: + return False + return True + +def color(canvas, p, old_val, new_val): + canvas.data[p.x][p.y] = new_val + +def find_neighbors(canvas, p, old_val, new_val): + # north, south, east, west neighbors + possible_neighbors = [ + Point(p.x, p.y+1), + Point(p.x+1, p.y), + Point(p.x-1, p.y), + Point(p.x, p.y-1) + ] + + # exclude the neighbors that go out of bounds and should not be colored + neighbors = [] + for possible_neighbor in possible_neighbors: + if inbounds(canvas, possible_neighbor): + if canvas.data[possible_neighbor.x][possible_neighbor.y] == old_val: + neighbors.append(possible_neighbor) + return neighbors + +def stack_fill(canvas, p, old_val, new_val): + if old_val == new_val: + return + + S = list() + S.append(p) + + while len(S) > 0: + cur_loc = S.pop() + if canvas.data[cur_loc.x][cur_loc.y] == old_val: + color(canvas, cur_loc, old_val, new_val) + S+= find_neighbors(canvas, cur_loc, old_val, new_val) + +def queue_fill(canvas, p, old_val, new_val): + if old_val == new_val: + return + + Q = Queue() + Q.put(p) + + color(canvas, p, old_val, new_val) + + while not Q.empty(): + cur_loc = Q.get() + neighbors = find_neighbors(canvas, cur_loc, old_val, new_val) + + for neighbor in neighbors: + color(canvas, neighbor, old_val, new_val) + Q.put(neighbor) + +def recursive_fill(canvas, p, old_val, new_val): + if old_val == new_val: + return + + color(canvas, p, old_val, new_val) + + neighbors = find_neighbors(canvas, p, old_val, new_val) + for neighbor in neighbors: + recursive_fill(canvas, neighbor, old_val, new_val) + +def main(): + grid = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [1, 1, 1, 1, 1], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] + ] + answer = [ + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] + ] + + c0 = Canvas(5, 5, grid) + c1 = Canvas(5, 5, grid) + c2 = Canvas(5, 5, grid) + + start_loc = Point(0, 0) + + recursive_fill(c0, start_loc, 0, 1) + queue_fill(c1, start_loc, 0, 1) + stack_fill(c2, start_loc, 0, 1) + + assert c0.data == answer + assert c1.data == answer + assert c2.data == answer + + print("Tests Passed") + +if __name__ == "__main__": + main() diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index 836f86897..b9cf2f3c1 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -90,6 +90,8 @@ In code, this might look like this: [import:37-55, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:34-52, lang:"c"](code/c/flood_fill.c) +{% sample lang="py" %} +[import:15-30, lang="python"](code/python/flood_fill.py) {% endmethod %} @@ -106,6 +108,8 @@ In code, it might look like this: [import:106-118, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:180-195, lang:"c"](code/c/flood_fill.c) +{% sample lang="py" %} +[import:62-70, lang="python"](code/python/flood_fill.py) {% endmethod %} All code snippets for this chapter rely on an exterior `color` function, defined as @@ -115,6 +119,8 @@ All code snippets for this chapter rely on an exterior `color` function, defined [import:23-35, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:28-32, lang:"c"](code/c/flood_fill.c) +{% sample lang="py" %} +[import:12-13, lang="python"](code/python/flood_fill.py) {% endmethod %} The above code continues recursing through available neighbors as long as neighbors exist, and this should work so long as we are adding the correct set of neighbors. @@ -126,6 +132,8 @@ Additionally, it is possible to do the same type of traversal by managing a stac [import:57-77, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:85-108, lang:"c"](code/c/flood_fill.c) +{% sample lang="py" %} +[import:32-43, lang="python"](code/python/flood_fill.py) {% endmethod %} This is ultimately the same method of traversal as before; however, because we are managing our own data structure, there are a few distinct differences: @@ -165,6 +173,8 @@ The code would look something like this: [import:80-104, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:155-178, lang:"c"](code/c/flood_fill.c) +{% sample lang="py" %} +[import:45-60, lang="python"](code/python/flood_fill.py) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally. @@ -243,6 +253,8 @@ After, we will fill in the left-hand side of the array to be all ones by choosin [import, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import, lang:"c"](code/c/flood_fill.c) +{% sample lang="py" %} +[import:, lang="python"](code/python/flood_fill.py) {% endmethod %} From 041bc043572615be3c0521ae987607da4a9c4ce7 Mon Sep 17 00:00:00 2001 From: Ishaan Verma Date: Sat, 19 Sep 2020 19:06:14 +0530 Subject: [PATCH 2/7] add name to contributors --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8baea2e3d..7c37f92b5 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -53,3 +53,4 @@ This file lists everyone, who contributed to this repo and wanted to show up her - James Goytia - Amaras - Jonathan Dönszelmann +"- Ishaan Verma From 22b96a54a60c397db9854cb309adfe84f1e99258 Mon Sep 17 00:00:00 2001 From: Ishaan Verma Date: Sat, 19 Sep 2020 19:07:57 +0530 Subject: [PATCH 3/7] small correction --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7c37f92b5..37b6d127f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -53,4 +53,4 @@ This file lists everyone, who contributed to this repo and wanted to show up her - James Goytia - Amaras - Jonathan Dönszelmann -"- Ishaan Verma +- Ishaan Verma From 7cf164100de21551362afc3565a558142d4cddd3 Mon Sep 17 00:00:00 2001 From: Ishaan Verma Date: Sat, 19 Sep 2020 21:44:08 +0530 Subject: [PATCH 4/7] remove unnecessary argument to color() --- contents/flood_fill/code/python/flood_fill.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contents/flood_fill/code/python/flood_fill.py b/contents/flood_fill/code/python/flood_fill.py index f235d9bfd..5bc30bc00 100644 --- a/contents/flood_fill/code/python/flood_fill.py +++ b/contents/flood_fill/code/python/flood_fill.py @@ -9,7 +9,7 @@ def inbounds(canvas, p): return False return True -def color(canvas, p, old_val, new_val): +def color(canvas, p, new_val): canvas.data[p.x][p.y] = new_val def find_neighbors(canvas, p, old_val, new_val): @@ -39,7 +39,7 @@ def stack_fill(canvas, p, old_val, new_val): while len(S) > 0: cur_loc = S.pop() if canvas.data[cur_loc.x][cur_loc.y] == old_val: - color(canvas, cur_loc, old_val, new_val) + color(canvas, cur_loc, new_val) S+= find_neighbors(canvas, cur_loc, old_val, new_val) def queue_fill(canvas, p, old_val, new_val): @@ -49,21 +49,21 @@ def queue_fill(canvas, p, old_val, new_val): Q = Queue() Q.put(p) - color(canvas, p, old_val, new_val) + color(canvas, p, new_val) while not Q.empty(): cur_loc = Q.get() neighbors = find_neighbors(canvas, cur_loc, old_val, new_val) for neighbor in neighbors: - color(canvas, neighbor, old_val, new_val) + color(canvas, neighbor, new_val) Q.put(neighbor) def recursive_fill(canvas, p, old_val, new_val): if old_val == new_val: return - color(canvas, p, old_val, new_val) + color(canvas, p, new_val) neighbors = find_neighbors(canvas, p, old_val, new_val) for neighbor in neighbors: From 7ea3e01e1ddaa7a098b57e28767af7eefaa7996a Mon Sep 17 00:00:00 2001 From: Ishaan Verma Date: Sun, 4 Oct 2020 02:11:19 +0530 Subject: [PATCH 5/7] switch to using numpy + small corrections --- contents/flood_fill/code/python/flood_fill.py | 118 ++++++++++-------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/contents/flood_fill/code/python/flood_fill.py b/contents/flood_fill/code/python/flood_fill.py index 5bc30bc00..d043ae3ca 100644 --- a/contents/flood_fill/code/python/flood_fill.py +++ b/contents/flood_fill/code/python/flood_fill.py @@ -1,16 +1,14 @@ from collections import namedtuple from queue import Queue +import numpy as np Point = namedtuple("Point", "x y") -Canvas = namedtuple("Canvas", "max_x max_y data") -def inbounds(canvas, p): - if p.x < 0 or p.y < 0 or p.x >= canvas.max_x or p.y >= canvas.max_y: - return False - return True +def inbounds(canvas_shape, p): + return min(p) >= 0 and p.x < canvas_shape[0] and p.y < canvas_shape[1] def color(canvas, p, new_val): - canvas.data[p.x][p.y] = new_val + canvas[p] = new_val def find_neighbors(canvas, p, old_val, new_val): # north, south, east, west neighbors @@ -24,8 +22,8 @@ def find_neighbors(canvas, p, old_val, new_val): # exclude the neighbors that go out of bounds and should not be colored neighbors = [] for possible_neighbor in possible_neighbors: - if inbounds(canvas, possible_neighbor): - if canvas.data[possible_neighbor.x][possible_neighbor.y] == old_val: + if inbounds(canvas.shape, possible_neighbor): + if canvas[possible_neighbor] == old_val: neighbors.append(possible_neighbor) return neighbors @@ -33,31 +31,29 @@ def stack_fill(canvas, p, old_val, new_val): if old_val == new_val: return - S = list() - S.append(p) + stack = [p] - while len(S) > 0: - cur_loc = S.pop() - if canvas.data[cur_loc.x][cur_loc.y] == old_val: - color(canvas, cur_loc, new_val) - S+= find_neighbors(canvas, cur_loc, old_val, new_val) + while stack: + cur_loc = stack.pop() + color(canvas, cur_loc, new_val) + stack += find_neighbors(canvas, cur_loc, old_val, new_val) def queue_fill(canvas, p, old_val, new_val): if old_val == new_val: return - Q = Queue() - Q.put(p) + q = Queue() + q.put(p) color(canvas, p, new_val) - while not Q.empty(): - cur_loc = Q.get() + while not q.empty(): + cur_loc = q.get() neighbors = find_neighbors(canvas, cur_loc, old_val, new_val) for neighbor in neighbors: color(canvas, neighbor, new_val) - Q.put(neighbor) + q.put(neighbor) def recursive_fill(canvas, p, old_val, new_val): if old_val == new_val: @@ -69,37 +65,53 @@ def recursive_fill(canvas, p, old_val, new_val): for neighbor in neighbors: recursive_fill(canvas, neighbor, old_val, new_val) -def main(): - grid = [ - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0], - [1, 1, 1, 1, 1], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0] - ] - answer = [ - [1, 1, 1, 1, 1], - [1, 1, 1, 1, 1], - [1, 1, 1, 1, 1], - [0, 0, 0, 0, 0], - [0, 0, 0, 0, 0] - ] - - c0 = Canvas(5, 5, grid) - c1 = Canvas(5, 5, grid) - c2 = Canvas(5, 5, grid) - - start_loc = Point(0, 0) - - recursive_fill(c0, start_loc, 0, 1) - queue_fill(c1, start_loc, 0, 1) - stack_fill(c2, start_loc, 0, 1) - - assert c0.data == answer - assert c1.data == answer - assert c2.data == answer - - print("Tests Passed") - if __name__ == "__main__": - main() + TestResults = namedtuple('TestResults', 'passes failures') + pass_count = failure_count = 0 + + grid = np.zeros((5, 5)) + grid[2,:] = 1 + solution_grid = np.zeros((5, 5)) + solution_grid[:3,] = 1 + + starting_location = Point(0, 0) + + + # The following is manual unit testing of the function + recursive_fill(grid, starting_location, 0, 1) + try: + assert (grid == solution_grid).all() + except AssertionError: + print('F', end='') + failure_count += 1 + else: + print('.', end='') + pass_count += 1 + + # Resetting the grid, if everything went well. + grid[:2,] = 0 + + stack_fill(grid, starting_location, 0, 1) + try: + assert (grid == solution_grid).all() + except AssertionError: + print('F', end='') + failure_count += 1 + else: + print('.', end='') + pass_count += 1 + + grid[:2,] = 0 + + queue_fill(grid, starting_location, 0, 1) + try: + assert (grid == solution_grid).all() + except AssertionError: + print('F', end='') + failure_count += 1 + else: + print('.', end='') + pass_count += 1 + + print('') + print(TestResults(pass_count, failure_count)) From 8d1b1c242b56f56fc359c667b7eeac981a7d51db Mon Sep 17 00:00:00 2001 From: Ishaan Verma Date: Sun, 4 Oct 2020 03:34:36 +0530 Subject: [PATCH 6/7] improved unit testing --- contents/flood_fill/code/python/flood_fill.py | 71 ++++++------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/contents/flood_fill/code/python/flood_fill.py b/contents/flood_fill/code/python/flood_fill.py index d043ae3ca..0d22599f5 100644 --- a/contents/flood_fill/code/python/flood_fill.py +++ b/contents/flood_fill/code/python/flood_fill.py @@ -65,53 +65,28 @@ def recursive_fill(canvas, p, old_val, new_val): for neighbor in neighbors: recursive_fill(canvas, neighbor, old_val, new_val) -if __name__ == "__main__": - TestResults = namedtuple('TestResults', 'passes failures') - pass_count = failure_count = 0 - +def main(): grid = np.zeros((5, 5)) grid[2,:] = 1 - solution_grid = np.zeros((5, 5)) - solution_grid[:3,] = 1 - - starting_location = Point(0, 0) - - - # The following is manual unit testing of the function - recursive_fill(grid, starting_location, 0, 1) - try: - assert (grid == solution_grid).all() - except AssertionError: - print('F', end='') - failure_count += 1 - else: - print('.', end='') - pass_count += 1 - - # Resetting the grid, if everything went well. - grid[:2,] = 0 - - stack_fill(grid, starting_location, 0, 1) - try: - assert (grid == solution_grid).all() - except AssertionError: - print('F', end='') - failure_count += 1 - else: - print('.', end='') - pass_count += 1 - - grid[:2,] = 0 - - queue_fill(grid, starting_location, 0, 1) - try: - assert (grid == solution_grid).all() - except AssertionError: - print('F', end='') - failure_count += 1 - else: - print('.', end='') - pass_count += 1 - - print('') - print(TestResults(pass_count, failure_count)) + + answer = np.zeros((5, 5)) + answer[:3,] = 1 + + c0 = grid.copy() + c1 = grid.copy() + c2 = grid.copy() + + start_loc = Point(0, 0) + + recursive_fill(c0, start_loc, 0, 1) + queue_fill(c1, start_loc, 0, 1) + stack_fill(c2, start_loc, 0, 1) + + assert (c0 == answer).all() + assert (c1 == answer).all() + assert (c2 == answer).all() + + print("Tests Passed") + +if __name__ == "__main__": + main() From cd8f00fcc0a92f46754a554f3538093387e74570 Mon Sep 17 00:00:00 2001 From: James Schloss Date: Mon, 12 Oct 2020 18:38:55 -0400 Subject: [PATCH 7/7] Apply suggestions from code review --- contents/flood_fill/flood_fill.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index b9cf2f3c1..9cf00c577 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -91,7 +91,7 @@ In code, this might look like this: {% sample lang="c" %} [import:34-52, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} -[import:15-30, lang="python"](code/python/flood_fill.py) +[import:13-28, lang="python"](code/python/flood_fill.py) {% endmethod %} @@ -109,7 +109,7 @@ In code, it might look like this: {% sample lang="c" %} [import:180-195, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} -[import:62-70, lang="python"](code/python/flood_fill.py) +[import:58-66, lang="python"](code/python/flood_fill.py) {% endmethod %} All code snippets for this chapter rely on an exterior `color` function, defined as @@ -120,7 +120,7 @@ All code snippets for this chapter rely on an exterior `color` function, defined {% sample lang="c" %} [import:28-32, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} -[import:12-13, lang="python"](code/python/flood_fill.py) +[import:10-11, lang="python"](code/python/flood_fill.py) {% endmethod %} The above code continues recursing through available neighbors as long as neighbors exist, and this should work so long as we are adding the correct set of neighbors. @@ -133,7 +133,7 @@ Additionally, it is possible to do the same type of traversal by managing a stac {% sample lang="c" %} [import:85-108, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} -[import:32-43, lang="python"](code/python/flood_fill.py) +[import:30-39, lang="python"](code/python/flood_fill.py) {% endmethod %} This is ultimately the same method of traversal as before; however, because we are managing our own data structure, there are a few distinct differences: @@ -174,7 +174,7 @@ The code would look something like this: {% sample lang="c" %} [import:155-178, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} -[import:45-60, lang="python"](code/python/flood_fill.py) +[import:41-56, lang="python"](code/python/flood_fill.py) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally.