Skip to content

Commit 627aed9

Browse files
lazypropleios
andauthored
Add Flood Fill in Python (#753)
* add floodfill in python * add name to contributors * small correction * remove unnecessary argument to color() * switch to using numpy + small corrections * improved unit testing * Apply suggestions from code review Co-authored-by: James Schloss <jrs.schloss@gmail.com>
1 parent 9dcc06f commit 627aed9

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ This file lists everyone, who contributed to this repo and wanted to show up her
5353
- James Goytia
5454
- Amaras
5555
- Jonathan Dönszelmann
56+
- Ishaan Verma
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from collections import namedtuple
2+
from queue import Queue
3+
import numpy as np
4+
5+
Point = namedtuple("Point", "x y")
6+
7+
def inbounds(canvas_shape, p):
8+
return min(p) >= 0 and p.x < canvas_shape[0] and p.y < canvas_shape[1]
9+
10+
def color(canvas, p, new_val):
11+
canvas[p] = new_val
12+
13+
def find_neighbors(canvas, p, old_val, new_val):
14+
# north, south, east, west neighbors
15+
possible_neighbors = [
16+
Point(p.x, p.y+1),
17+
Point(p.x+1, p.y),
18+
Point(p.x-1, p.y),
19+
Point(p.x, p.y-1)
20+
]
21+
22+
# exclude the neighbors that go out of bounds and should not be colored
23+
neighbors = []
24+
for possible_neighbor in possible_neighbors:
25+
if inbounds(canvas.shape, possible_neighbor):
26+
if canvas[possible_neighbor] == old_val:
27+
neighbors.append(possible_neighbor)
28+
return neighbors
29+
30+
def stack_fill(canvas, p, old_val, new_val):
31+
if old_val == new_val:
32+
return
33+
34+
stack = [p]
35+
36+
while stack:
37+
cur_loc = stack.pop()
38+
color(canvas, cur_loc, new_val)
39+
stack += find_neighbors(canvas, cur_loc, old_val, new_val)
40+
41+
def queue_fill(canvas, p, old_val, new_val):
42+
if old_val == new_val:
43+
return
44+
45+
q = Queue()
46+
q.put(p)
47+
48+
color(canvas, p, new_val)
49+
50+
while not q.empty():
51+
cur_loc = q.get()
52+
neighbors = find_neighbors(canvas, cur_loc, old_val, new_val)
53+
54+
for neighbor in neighbors:
55+
color(canvas, neighbor, new_val)
56+
q.put(neighbor)
57+
58+
def recursive_fill(canvas, p, old_val, new_val):
59+
if old_val == new_val:
60+
return
61+
62+
color(canvas, p, new_val)
63+
64+
neighbors = find_neighbors(canvas, p, old_val, new_val)
65+
for neighbor in neighbors:
66+
recursive_fill(canvas, neighbor, old_val, new_val)
67+
68+
def main():
69+
grid = np.zeros((5, 5))
70+
grid[2,:] = 1
71+
72+
answer = np.zeros((5, 5))
73+
answer[:3,] = 1
74+
75+
c0 = grid.copy()
76+
c1 = grid.copy()
77+
c2 = grid.copy()
78+
79+
start_loc = Point(0, 0)
80+
81+
recursive_fill(c0, start_loc, 0, 1)
82+
queue_fill(c1, start_loc, 0, 1)
83+
stack_fill(c2, start_loc, 0, 1)
84+
85+
assert (c0 == answer).all()
86+
assert (c1 == answer).all()
87+
assert (c2 == answer).all()
88+
89+
print("Tests Passed")
90+
91+
if __name__ == "__main__":
92+
main()

contents/flood_fill/flood_fill.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ In code, this might look like this:
9090
[import:37-55, lang:"julia"](code/julia/flood_fill.jl)
9191
{% sample lang="c" %}
9292
[import:34-52, lang:"c"](code/c/flood_fill.c)
93+
{% sample lang="py" %}
94+
[import:13-28, lang="python"](code/python/flood_fill.py)
9395
{% endmethod %}
9496

9597

@@ -106,6 +108,8 @@ In code, it might look like this:
106108
[import:106-118, lang:"julia"](code/julia/flood_fill.jl)
107109
{% sample lang="c" %}
108110
[import:180-195, lang:"c"](code/c/flood_fill.c)
111+
{% sample lang="py" %}
112+
[import:58-66, lang="python"](code/python/flood_fill.py)
109113
{% endmethod %}
110114

111115
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
115119
[import:23-35, lang:"julia"](code/julia/flood_fill.jl)
116120
{% sample lang="c" %}
117121
[import:28-32, lang:"c"](code/c/flood_fill.c)
122+
{% sample lang="py" %}
123+
[import:10-11, lang="python"](code/python/flood_fill.py)
118124
{% endmethod %}
119125

120126
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
126132
[import:57-77, lang:"julia"](code/julia/flood_fill.jl)
127133
{% sample lang="c" %}
128134
[import:85-108, lang:"c"](code/c/flood_fill.c)
135+
{% sample lang="py" %}
136+
[import:30-39, lang="python"](code/python/flood_fill.py)
129137
{% endmethod %}
130138

131139
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:
165173
[import:80-104, lang:"julia"](code/julia/flood_fill.jl)
166174
{% sample lang="c" %}
167175
[import:155-178, lang:"c"](code/c/flood_fill.c)
176+
{% sample lang="py" %}
177+
[import:41-56, lang="python"](code/python/flood_fill.py)
168178
{% endmethod %}
169179

170180
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
243253
[import, lang:"julia"](code/julia/flood_fill.jl)
244254
{% sample lang="c" %}
245255
[import, lang:"c"](code/c/flood_fill.c)
256+
{% sample lang="py" %}
257+
[import:, lang="python"](code/python/flood_fill.py)
246258
{% endmethod %}
247259

248260

0 commit comments

Comments
 (0)