From 408ca92aab52524338bb56e744a8cf077e93d7f8 Mon Sep 17 00:00:00 2001 From: Sammy Plat Date: Sun, 22 Aug 2021 23:45:38 +0200 Subject: [PATCH 1/4] Re-added flood fill in Coconut (without the stupid master merge) --- .../flood_fill/code/coconut/flood_fill.coco | 128 ++++++++++++++++++ contents/flood_fill/flood_fill.md | 8 ++ 2 files changed, 136 insertions(+) create mode 100644 contents/flood_fill/code/coconut/flood_fill.coco diff --git a/contents/flood_fill/code/coconut/flood_fill.coco b/contents/flood_fill/code/coconut/flood_fill.coco new file mode 100644 index 000000000..9b0d49d9e --- /dev/null +++ b/contents/flood_fill/code/coconut/flood_fill.coco @@ -0,0 +1,128 @@ +from collections import deque +import numpy as np + + +data Point(x, y): + def __add__(self, other is Point) = Point(self.x + other.x, self.y + other.y) + + +# This function is necessary, because negative indices wrap around the +# array in Coconut. +def inbounds(canvas_shape, location is Point) = + min(location) >= 0 and location.x < canvas_shape[0] and location.y < canvas_shape[1] + + +def colour(canvas, location is Point, old_value, new_value): + if not inbounds(canvas.shape, location): + return + + if canvas[location] != old_value: + return + else: + canvas[location] = new_value + + +def find_neighbours(canvas, location is Point, old_value, new_value): + possible_neighbours = ((Point(0, 1), Point(1, 0), Point(0, -1), Point(-1, 0)) + |> map$(location.__add__)) + + yield from possible_neighbours |> filter$(x -> (inbounds(canvas.shape, x) and canvas[x] == old_value)) + + +def stack_fill(canvas, location is Point, old_value, new_value): + if new_value == old_value or not inbounds(canvas.shape, location): + return + + stack = [location] + + while stack: + current_location = stack.pop() + if canvas[current_location] == old_value: + canvas[current_location] = new_value + for neighbour in find_neighbours(canvas, current_location, old_value, + new_value): + stack.append(neighbour) + + +def queue_fill(canvas, location is Point, old_value, new_value): + if new_value == old_value or not inbounds(canvas.shape, location): + return + + queue = deque() + queue.append(location) + + canvas[location] = new_value + + while queue: + current_location = queue.popleft() + for neighbour in find_neighbours(canvas, current_location, old_value, + new_value): + queue.append(neighbour) + canvas[neighbour] = new_value + + +def recursive_fill(canvas, location is Point, old_value, new_value): + if new_value == old_value or not inbounds(canvas.shape, location): + return + + canvas[location] = new_value + # consume is important here, because otherwise, the recursive function is not called again + consume( + find_neighbours(canvas, location, old_value, new_value) + |> map$(recursive_fill$(canvas, ?, old_value, new_value)) + ) + +if __name__ == '__main__': + # Testing setup + from collections import namedtuple + + 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)) + diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index 87ec2b7a7..0014220d6 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -110,6 +110,8 @@ In code, it might look like this: [import:174-189, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} [import:55-63, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:25-29, lang:"coconut"](code/coconut/flood_fill.coco) {% 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. @@ -123,6 +125,8 @@ Additionally, it is possible to do the same type of traversal by managing a stac [import:79-102, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} [import:27-36, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:64-73, lang:"coconut"](code/coconut/flood_fill.coco) {% 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: @@ -164,6 +168,8 @@ The code would look something like this: [import:149-172, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} [import:38-53, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:47-61, lang:"coconut"](code/coconut/flood_fill.coco) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally. @@ -244,6 +250,8 @@ After, we will fill in the left-hand side of the array to be all ones by choosin [import, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} [import:, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import, lang="coconut"](code/coconut/flood_fill.coco) {% endmethod %} From 225bd1cd853f0a1e9669d62ca9fe30c9e6300f81 Mon Sep 17 00:00:00 2001 From: Sammy Plat Date: Tue, 31 Aug 2021 23:45:09 +0200 Subject: [PATCH 2/4] Removed the 'colour' function, reworked the md, and added a test function --- .../flood_fill/code/coconut/flood_fill.coco | 29 ++++++++----------- contents/flood_fill/flood_fill.md | 8 +++-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/contents/flood_fill/code/coconut/flood_fill.coco b/contents/flood_fill/code/coconut/flood_fill.coco index 9b0d49d9e..f88e77c25 100644 --- a/contents/flood_fill/code/coconut/flood_fill.coco +++ b/contents/flood_fill/code/coconut/flood_fill.coco @@ -12,21 +12,12 @@ def inbounds(canvas_shape, location is Point) = min(location) >= 0 and location.x < canvas_shape[0] and location.y < canvas_shape[1] -def colour(canvas, location is Point, old_value, new_value): - if not inbounds(canvas.shape, location): - return - - if canvas[location] != old_value: - return - else: - canvas[location] = new_value - - def find_neighbours(canvas, location is Point, old_value, new_value): possible_neighbours = ((Point(0, 1), Point(1, 0), Point(0, -1), Point(-1, 0)) |> map$(location.__add__)) - yield from possible_neighbours |> filter$(x -> (inbounds(canvas.shape, x) and canvas[x] == old_value)) + yield from possible_neighbours |> filter$(x -> (inbounds(canvas.shape, x) + and canvas[x] == old_value)) def stack_fill(canvas, location is Point, old_value, new_value): @@ -39,9 +30,8 @@ def stack_fill(canvas, location is Point, old_value, new_value): current_location = stack.pop() if canvas[current_location] == old_value: canvas[current_location] = new_value - for neighbour in find_neighbours(canvas, current_location, old_value, - new_value): - stack.append(neighbour) + stack.extend(find_neighbours(canvas, current_location, old_value, + new_value)) def queue_fill(canvas, location is Point, old_value, new_value): @@ -57,8 +47,8 @@ def queue_fill(canvas, location is Point, old_value, new_value): current_location = queue.popleft() for neighbour in find_neighbours(canvas, current_location, old_value, new_value): - queue.append(neighbour) canvas[neighbour] = new_value + queue.append(neighbour) def recursive_fill(canvas, location is Point, old_value, new_value): @@ -72,8 +62,7 @@ def recursive_fill(canvas, location is Point, old_value, new_value): |> map$(recursive_fill$(canvas, ?, old_value, new_value)) ) -if __name__ == '__main__': - # Testing setup +def test(): from collections import namedtuple TestResults = namedtuple('TestResults', 'passes failures') @@ -126,3 +115,9 @@ if __name__ == '__main__': print('') print(TestResults(pass_count, failure_count)) +if __name__ == '__main__': + # Testing setup + test() + + + diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index 0014220d6..185050fe1 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -92,6 +92,8 @@ In code, this might look like this: [import:28-46, lang:"c"](code/c/flood_fill.c) {% sample lang="py" %} [import:10-25, lang="python"](code/python/flood_fill.py) +{% sample lang="coco" %} +[import:15-19, lang="coconut"](code/coconut/flood_fill.coco) {% endmethod %} @@ -111,7 +113,7 @@ In code, it might look like this: {% sample lang="py" %} [import:55-63, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} -[import:25-29, lang:"coconut"](code/coconut/flood_fill.coco) +[import:54-63, lang:"coconut"](code/coconut/flood_fill.coco) {% 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,7 +128,7 @@ Additionally, it is possible to do the same type of traversal by managing a stac {% sample lang="py" %} [import:27-36, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} -[import:64-73, lang:"coconut"](code/coconut/flood_fill.coco) +[import:23-34, lang:"coconut"](code/coconut/flood_fill.coco) {% 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: @@ -169,7 +171,7 @@ The code would look something like this: {% sample lang="py" %} [import:38-53, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} -[import:47-61, lang:"coconut"](code/coconut/flood_fill.coco) +[import:37-51, lang:"coconut"](code/coconut/flood_fill.coco) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally. From 664353901d727c8e4a605090b228cf5de7cfcc69 Mon Sep 17 00:00:00 2001 From: Sammy Plat Date: Mon, 6 Sep 2021 19:17:20 +0200 Subject: [PATCH 3/4] taken berquist's review into account --- .../flood_fill/code/coconut/flood_fill.coco | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/contents/flood_fill/code/coconut/flood_fill.coco b/contents/flood_fill/code/coconut/flood_fill.coco index f88e77c25..f47892e3a 100644 --- a/contents/flood_fill/code/coconut/flood_fill.coco +++ b/contents/flood_fill/code/coconut/flood_fill.coco @@ -62,6 +62,12 @@ def recursive_fill(canvas, location is Point, old_value, new_value): |> map$(recursive_fill$(canvas, ?, old_value, new_value)) ) + +def test_grid(initial_canvas, final_canvas, function): + canvas = initial_canvas.copy() # ensure the initial_canvas is unchanged + function(canvas) + return (canvas == final_canvas).all() + def test(): from collections import namedtuple @@ -75,49 +81,35 @@ def test(): starting_location = Point(0, 0) - + recursive_test_func = recursive_fill$(?, starting_location, 0, 1) # 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 + if test_grid(grid, solution_grid, recursive_test_func): + pass_count += 1 + print('.', end='') else: + failure_count += 1 + print('F', end='') + + stack_test_func = stack_fill$(?, starting_location, 0, 1) + if test_grid(grid, solution_grid, stack_test_func): 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: + else: print('F', end='') failure_count += 1 - else: + + queue_test_func = queue_fill$(?, starting_location, 0, 1) + if test_grid(grid, solution_grid, queue_test_func): print('.', end='') pass_count += 1 - - grid[:2,] = 0 - - queue_fill(grid, starting_location, 0, 1) - try: - assert (grid == solution_grid).all() - except AssertionError: + else: print('F', end='') failure_count += 1 - else: - print('.', end='') - pass_count += 1 - print('') + print() print(TestResults(pass_count, failure_count)) if __name__ == '__main__': # Testing setup test() - - From a8e5cbf57d85750452d3adeaa6a276745934c1f9 Mon Sep 17 00:00:00 2001 From: Sammy Plat Date: Mon, 6 Sep 2021 19:52:15 +0200 Subject: [PATCH 4/4] removed redundant new_value in find_neighbours --- contents/flood_fill/code/coconut/flood_fill.coco | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/contents/flood_fill/code/coconut/flood_fill.coco b/contents/flood_fill/code/coconut/flood_fill.coco index f47892e3a..f2580a608 100644 --- a/contents/flood_fill/code/coconut/flood_fill.coco +++ b/contents/flood_fill/code/coconut/flood_fill.coco @@ -12,7 +12,7 @@ def inbounds(canvas_shape, location is Point) = min(location) >= 0 and location.x < canvas_shape[0] and location.y < canvas_shape[1] -def find_neighbours(canvas, location is Point, old_value, new_value): +def find_neighbours(canvas, location is Point, old_value): possible_neighbours = ((Point(0, 1), Point(1, 0), Point(0, -1), Point(-1, 0)) |> map$(location.__add__)) @@ -30,8 +30,7 @@ def stack_fill(canvas, location is Point, old_value, new_value): current_location = stack.pop() if canvas[current_location] == old_value: canvas[current_location] = new_value - stack.extend(find_neighbours(canvas, current_location, old_value, - new_value)) + stack.extend(find_neighbours(canvas, current_location, old_value)) def queue_fill(canvas, location is Point, old_value, new_value): @@ -45,8 +44,7 @@ def queue_fill(canvas, location is Point, old_value, new_value): while queue: current_location = queue.popleft() - for neighbour in find_neighbours(canvas, current_location, old_value, - new_value): + for neighbour in find_neighbours(canvas, current_location, old_value): canvas[neighbour] = new_value queue.append(neighbour) @@ -58,7 +56,7 @@ def recursive_fill(canvas, location is Point, old_value, new_value): canvas[location] = new_value # consume is important here, because otherwise, the recursive function is not called again consume( - find_neighbours(canvas, location, old_value, new_value) + find_neighbours(canvas, location, old_value) |> map$(recursive_fill$(canvas, ?, old_value, new_value)) )