Skip to content

Added flood fill in Coconut #743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a18e7cc
Adding flood fill in Coconut
Amaras Aug 5, 2020
7d2739b
Correcting copy-paste fails
Amaras Aug 5, 2020
5da3ae8
Flood Fill in C (#741)
Gathros Aug 6, 2020
d21dacd
Huffman Encoding Scratch Implementation (#682)
dovisutu Sep 10, 2020
aa08db0
Update monte_carlo.py (#751)
DougBR Sep 10, 2020
a4dbc2a
Fixed infinite loop issue in "euclidean_example.py" (#749)
valdaarhun Sep 11, 2020
9dcc06f
fix both error of issue 739 (#756)
Oct 12, 2020
627aed9
Add Flood Fill in Python (#753)
lazyprop Oct 12, 2020
fee7e83
Remove color function from flood fill because it is redundant (#766)
leios Oct 15, 2020
85f8e9a
Added Bogosort in Coconut (#735)
Amaras Oct 15, 2020
18944ea
Added Bubblesort in Coconut (#736)
Amaras Oct 15, 2020
f1aa057
Add Scheme alongside Racket in EditorConfig (#779)
berquist Nov 4, 2020
7bc0e22
Added CSS for printing (#777)
0xJonas Nov 5, 2020
91d5625
Added Cooley-Tukey in Lisp
0xJonas Nov 11, 2020
5a6ebd0
Make Euclid's algorithm in Nim more idiomatic (#784)
berquist Nov 11, 2020
153317d
Make Verlet integration in Nim more idiomatic (#782)
berquist Nov 11, 2020
0ca5ac6
Make Bogosort in Nim more idiomatic (#783)
berquist Nov 11, 2020
7ada7da
Formatting and refactoring
0xJonas Dec 27, 2020
c72caaf
Update contents/cooley_tukey/code/clisp/fft.lisp
0xJonas Dec 28, 2020
70822e9
Update contents/cooley_tukey/code/clisp/fft.lisp
0xJonas Dec 28, 2020
9d0a5bb
Update contents/cooley_tukey/code/clisp/fft.lisp
0xJonas Dec 28, 2020
2793edf
Update contents/cooley_tukey/code/clisp/fft.lisp
0xJonas Dec 28, 2020
8426216
Update contents/cooley_tukey/code/clisp/fft.lisp
0xJonas Dec 28, 2020
e92bcb7
Update contents/cooley_tukey/code/clisp/fft.lisp
0xJonas Dec 28, 2020
e631b57
Updated line number
0xJonas Dec 28, 2020
02dd353
Merge pull request #786 from 0xJonas/cooley-tukey-lisp
Trashtalk217 Jan 1, 2021
f9a6eda
Add Computus in Nim (#780)
berquist Jan 2, 2021
b49f034
Domain Coloring in Python using Matplotlib (#788)
0xJonas Jan 4, 2021
9d7349a
adding gif to show people how to change languages (#797)
leios Jan 25, 2021
5be74ec
Adding flood fill in Coconut
Amaras Aug 5, 2020
1949a10
Correcting copy-paste fails
Amaras Aug 5, 2020
a5c0f6b
Addressed part of Trashtalk's review
Amaras Feb 4, 2021
db00314
Merge remote-tracking branch 'origin/flood_fill_coconut' into flood_f…
Amaras Feb 4, 2021
f312994
Got closer to Julia's code logic + reworked the line numbers
Amaras Feb 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions contents/flood_fill/code/coconut/flood_fill.coco
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is no longer used in the Julia implementation for it's redundancy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to rework the code to remove that function, indeed

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__)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP8 recommends a maximum line length of 79. And while I think 79 is a very low for line length, this line in particular is a bit long.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using a 90-ish line length (following Raymond Hettinger's advice), but this is indeed quite a bit too long. It probably needs the map on the next line, along with the possible filter


for neighbour in possible_neighbours:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this be a filter, those are usually available in most functional languages

if inbounds(canvas.shape, neighbour) and canvas[neighbour] == old_value:
yield neighbour


def stack_fill(canvas, location is Point, old_value, new_value):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll agree with you that it's better to do stack work with imperative code. Don't try to squeeze functional code into this.

if new_value == old_value:
return

stack = [location]

while stack:
current_location = stack.pop()
colour(canvas, current_location, old_value, new_value)
for neighbour in find_neighbours(canvas, current_location, old_value,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use stack.extend to add all new neighbours at once

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one, I think it's a good idea

new_value):
stack.append(neighbour)


def queue_fill(canvas, location is Point, old_value, new_value):
if new_value == old_value:
return

queue = deque()
queue.append(location)

colour(canvas, location, old_value, new_value)

while queue:
current_location = queue.popleft()
for neighbour in find_neighbours(canvas, current_location, old_value,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing as above, you could do map for colour and queue.extend to add.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I see how to do that right away. I probably need to work on it a bit more, but that seems out of my capabilities for now.

new_value):
queue.append(neighbour)
colour(canvas, neighbour, old_value, new_value)


def recursive_fill(canvas, location is Point, old_value, new_value):
if new_value == old_value:
return
colour(canvas, location, old_value, new_value)

for neighbour in find_neighbours(canvas, location, old_value, new_value):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a perfect candidate for a map and a lambda-function:

map (x -> recursive_fill(canvas, x, old_value, new_value)) (find_neighbours etc...)

The current implementation works of course, but it seemed like you wanted to have more functionalness.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hum... something like that?

find_neighbours(canvas, location, old_value, new_value) |> map$(recursive_fill$(canvas, ?, old_value, new_value)

Probably, yes. I am not sure it won't be too confusing, though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's definitely a little too long, splitting it up in neighbours = find_neighbours(...) and neighbours |> map$(...) is probably a good idea.

About the confusion bit. I think that maps are as intuitive as for-loops. It's just that a lot of people only know for-loops, so maps seem weird to them. I may be slightly (very) biased here, as I have some experience with functional programming, so keep that in mind.

recursive_fill(canvas, neighbour, old_value, new_value)


if __name__ == '__main__':
# Testing setup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe try to make the tests similair to the Julia tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean "similar to the Julia tests"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disregard the thing I said. I meant to say that you should probably use the same test as in the Julia implementation, namely:

grid = zeros(5,5)
grid[3,:] .= 1

But I just realize that you are already doing so. It's just that Julia indexes it's arrays starting at 1. Got confused there.

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='')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The . is a bit vague, maybe print something slightly more obvious S for succes, or maybe just full words: Success and Failure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it to look like how unittest runs tests, so that's a revertible design choice

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))

10 changes: 10 additions & 0 deletions contents/flood_fill/flood_fill.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ In code, this might look like this:
{% method %}
{% sample lang="jl" %}
[import:37-55, lang:"julia"](code/julia/flood_fill.jl)
{% sample lang="coco" %}
[import:25-30, lang:"coconut"](code/coconut/flood_fill.coco)
{% endmethod %}


Expand All @@ -106,6 +108,8 @@ In code, it might look like this:
All Julia code snippets for this chapter rely on an exterior `color!(...)` function, defined as

[import:23-35, lang:"julia"](code/julia/flood_fill.jl)
{% sample lang="coco" %}
[import:64-70, 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.
Expand All @@ -115,6 +119,8 @@ Additionally, it is possible to do the same type of traversal by managing a stac
{% method %}
{% sample lang="jl" %}
[import:57-77, lang:"julia"](code/julia/flood_fill.jl)
{% sample lang="coco" %}
[import:33-44, 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:
Expand Down Expand Up @@ -152,6 +158,8 @@ The code would look something like this:
{% method %}
{% sample lang="jl" %}
[import:80-104, lang:"julia"](code/julia/flood_fill.jl)
{% 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.
Expand Down Expand Up @@ -228,6 +236,8 @@ After, we will fill in the left-hand side of the array to be all ones by choosin
{% method %}
{% sample lang="jl" %}
[import, lang:"julia"](code/julia/flood_fill.jl)
{% sample lang="coco" %}
[import, lang:"coconut"](code/coconut/flood_fill.coco)
{% endmethod %}


Expand Down