Skip to content

Commit 09168eb

Browse files
authored
Flood fill in Coconut (#836)
1 parent 84e60b2 commit 09168eb

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from collections import deque
2+
import numpy as np
3+
4+
5+
data Point(x, y):
6+
def __add__(self, other is Point) = Point(self.x + other.x, self.y + other.y)
7+
8+
9+
# This function is necessary, because negative indices wrap around the
10+
# array in Coconut.
11+
def inbounds(canvas_shape, location is Point) =
12+
min(location) >= 0 and location.x < canvas_shape[0] and location.y < canvas_shape[1]
13+
14+
15+
def find_neighbours(canvas, location is Point, old_value):
16+
possible_neighbours = ((Point(0, 1), Point(1, 0), Point(0, -1), Point(-1, 0))
17+
|> map$(location.__add__))
18+
19+
yield from possible_neighbours |> filter$(x -> (inbounds(canvas.shape, x)
20+
and canvas[x] == old_value))
21+
22+
23+
def stack_fill(canvas, location is Point, old_value, new_value):
24+
if new_value == old_value or not inbounds(canvas.shape, location):
25+
return
26+
27+
stack = [location]
28+
29+
while stack:
30+
current_location = stack.pop()
31+
if canvas[current_location] == old_value:
32+
canvas[current_location] = new_value
33+
stack.extend(find_neighbours(canvas, current_location, old_value))
34+
35+
36+
def queue_fill(canvas, location is Point, old_value, new_value):
37+
if new_value == old_value or not inbounds(canvas.shape, location):
38+
return
39+
40+
queue = deque()
41+
queue.append(location)
42+
43+
canvas[location] = new_value
44+
45+
while queue:
46+
current_location = queue.popleft()
47+
for neighbour in find_neighbours(canvas, current_location, old_value):
48+
canvas[neighbour] = new_value
49+
queue.append(neighbour)
50+
51+
52+
def recursive_fill(canvas, location is Point, old_value, new_value):
53+
if new_value == old_value or not inbounds(canvas.shape, location):
54+
return
55+
56+
canvas[location] = new_value
57+
# consume is important here, because otherwise, the recursive function is not called again
58+
consume(
59+
find_neighbours(canvas, location, old_value)
60+
|> map$(recursive_fill$(canvas, ?, old_value, new_value))
61+
)
62+
63+
64+
def test_grid(initial_canvas, final_canvas, function):
65+
canvas = initial_canvas.copy() # ensure the initial_canvas is unchanged
66+
function(canvas)
67+
return (canvas == final_canvas).all()
68+
69+
def test():
70+
from collections import namedtuple
71+
72+
TestResults = namedtuple('TestResults', 'passes failures')
73+
pass_count = failure_count = 0
74+
75+
grid = np.zeros((5, 5))
76+
grid[2,:] = 1
77+
solution_grid = np.zeros((5, 5))
78+
solution_grid[:3,] = 1
79+
80+
starting_location = Point(0, 0)
81+
82+
recursive_test_func = recursive_fill$(?, starting_location, 0, 1)
83+
# The following is manual unit testing of the function
84+
if test_grid(grid, solution_grid, recursive_test_func):
85+
pass_count += 1
86+
print('.', end='')
87+
else:
88+
failure_count += 1
89+
print('F', end='')
90+
91+
stack_test_func = stack_fill$(?, starting_location, 0, 1)
92+
if test_grid(grid, solution_grid, stack_test_func):
93+
print('.', end='')
94+
pass_count += 1
95+
else:
96+
print('F', end='')
97+
failure_count += 1
98+
99+
queue_test_func = queue_fill$(?, starting_location, 0, 1)
100+
if test_grid(grid, solution_grid, queue_test_func):
101+
print('.', end='')
102+
pass_count += 1
103+
else:
104+
print('F', end='')
105+
failure_count += 1
106+
107+
print()
108+
print(TestResults(pass_count, failure_count))
109+
110+
if __name__ == '__main__':
111+
# Testing setup
112+
test()
113+

contents/flood_fill/flood_fill.md

+10
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ In code, this might look like this:
9292
[import:28-46, lang:"c"](code/c/flood_fill.c)
9393
{% sample lang="py" %}
9494
[import:10-25, lang="python"](code/python/flood_fill.py)
95+
{% sample lang="coco" %}
96+
[import:15-19, lang="coconut"](code/coconut/flood_fill.coco)
9597
{% endmethod %}
9698

9799

@@ -110,6 +112,8 @@ In code, it might look like this:
110112
[import:174-189, lang:"c"](code/c/flood_fill.c)
111113
{% sample lang="py" %}
112114
[import:55-63, lang="python"](code/python/flood_fill.py)
115+
{% sample lang="coco" %}
116+
[import:54-63, lang:"coconut"](code/coconut/flood_fill.coco)
113117
{% endmethod %}
114118

115119
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 +127,8 @@ Additionally, it is possible to do the same type of traversal by managing a stac
123127
[import:79-102, lang:"c"](code/c/flood_fill.c)
124128
{% sample lang="py" %}
125129
[import:27-36, lang="python"](code/python/flood_fill.py)
130+
{% sample lang="coco" %}
131+
[import:23-34, lang:"coconut"](code/coconut/flood_fill.coco)
126132
{% endmethod %}
127133

128134
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 +170,8 @@ The code would look something like this:
164170
[import:149-172, lang:"c"](code/c/flood_fill.c)
165171
{% sample lang="py" %}
166172
[import:38-53, lang="python"](code/python/flood_fill.py)
173+
{% sample lang="coco" %}
174+
[import:37-51, lang:"coconut"](code/coconut/flood_fill.coco)
167175
{% endmethod %}
168176

169177
Now, there is a small trick in this code that must be considered to make sure it runs optimally.
@@ -244,6 +252,8 @@ After, we will fill in the left-hand side of the array to be all ones by choosin
244252
[import, lang:"c"](code/c/flood_fill.c)
245253
{% sample lang="py" %}
246254
[import:, lang="python"](code/python/flood_fill.py)
255+
{% sample lang="coco" %}
256+
[import, lang="coconut"](code/coconut/flood_fill.coco)
247257
{% endmethod %}
248258

249259

0 commit comments

Comments
 (0)