-
-
Notifications
You must be signed in to change notification settings - Fork 359
Flood fill in C# #1011
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
base: main
Are you sure you want to change the base?
Flood fill in C# #1011
Changes from 2 commits
1cc3119
466ad37
607e1f5
8afaf73
bd16e72
49c96b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,190 @@ | ||||||||||||||||||||||||||
using System.Linq; | ||||||||||||||||||||||||||
using System.Diagnostics; | ||||||||||||||||||||||||||
using System.Collections.Generic; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
namespace Graphics | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
abstract class FloodFill | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
// A simple point on the 2 dimensional integer plane. | ||||||||||||||||||||||||||
private class Point2I | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
// Coordinates | ||||||||||||||||||||||||||
public int x; | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be a good idea here to consider encapsulating the x and y fields by making them private and exposing them through public properties. This adds a layer of protection and allows for future validation if needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that according to Object Oriented Programming principles all variables should have setter and getter functions. For that reason, sometimes I don't really like OOP as it takes simple things and makes them complicated (given that The program that I wrote is supposed to be as simple as possible and easy to understand for a beginner programmer and algorithm enthusiast that would like to make a first contact with the flood fill algorithm in C#. I feel that public int getX() { return x; }
public int getY() { return y; }
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; } would add extra complexity to the program. Although, no problem from my part, I could implement encapsulation for |
||||||||||||||||||||||||||
public int y; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
public Point2I(int x, int y) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
this.x = x; | ||||||||||||||||||||||||||
this.y = y; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
public override bool Equals(object o) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
if(!(o is Point2I other)) | ||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||
return this.x.Equals(other.x) && this.y.Equals(other.y); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
public override int GetHashCode() | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
int hash = 17; | ||||||||||||||||||||||||||
hash = 31 * hash + x; | ||||||||||||||||||||||||||
hash = 31 * hash + y; | ||||||||||||||||||||||||||
return hash; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Utility object for comparing List<List<float>> objects. | ||||||||||||||||||||||||||
private class FloatListEqualityComparer : IEqualityComparer<List<float>> | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
public bool Equals(List<float> one, List<float> two) | ||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
if (ReferenceEquals(one, two)) | ||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||
if (one == null || two == null) | ||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||
return one.SequenceEqual(two); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't you shorten this function like so?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, I'm afraid the initial function and the suggested changes are not equivalent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was too late, I used the wrong operator. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes that's better. It didn't cross my mind that you made a typo, I'm sorry. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
public int GetHashCode(List<float> list) { | ||||||||||||||||||||||||||
return list.GetHashCode(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
private static bool InBounds(Point2I size, Point2I loc) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing to consider here would be the readability to know what the variables of "siz" and "loc" are trying to convey. It looks as though size is supposed to convey bounds and loc is trying to convey a point. Variable name change suggestion:
|
||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||
if (loc.x < 0 || loc.y < 0) { | ||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
return loc.x < size.x && loc.y < size.y; | ||||||||||||||||||||||||||
|
if (loc.x < 0 || loc.y < 0) { | |
return false; | |
} | |
return loc.x < size.x && loc.y < size.y; | |
return loc.x >= 0 && loc.y >= 0 && loc.x < size.x && loc.y < size.y; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, these are equivalent. It is my personal preference to write boolean expressions that aren't too long, although this has no effect on the program's logic. I could shorten the boolean expression just like you suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer the orig if possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be preferred to use the original for readability.
If you are wanting to shorten and since there is only two options for return you could use a ternary operator here.
Readability > Complexity for C# Code is what I will always suggest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this instance I don't think it is necessary for you to use the ref keyword with:
ref List<List<float>> grid
Since you are only reading from the grid and not modifying it, there is no need to use the ref keyword.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't do it for read/write reasons, I did it for passing quickly the data structure into the function in
From what I've understood from C#, since the ref
keyword exists then it's like C++ where everything is passed by value except if you say "pass by reference" explicitly (C++ uses the &
symbol). This way, since List<List<float>>
is an object of
const List<List<float>>& grid
in order to avoid copying the entire data structure.
Keep in mind that I am using my C++ experience and making some assumptions that the inner mechanisms of C# work in the same way. If what I'm saying is wrong and there is no correspondence between the two languages feel free to correct me.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
presumably, you don't need to recompute size
every loop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case the grid (canvas) was not square but let's say it was:
new List<float>(){0, 0, 1, 0},
new List<float>(){0, 0, 1, 0, 0, 0, 0},
new List<float>(){0, 0, 1, 0, 0, 0},
new List<float>(){8, 0, 1, 1, 1, 1, 1, 1, 1, 1},
This way, the size check is necassary for every loop. Also, in my defense, this is the corresponding for-loop in the C++ implementation:
for (auto const& possible_neighbor : possible_neighbors) {
const auto size = CartesianIndex{ static_cast<int>(grid[0].size()), static_cast<int>(grid.size()) };
const auto x = static_cast<std::size_t>(possible_neighbor[0]);
const auto y = static_cast<std::size_t>(possible_neighbor[1]);
if (inbounds(size, possible_neighbor) && grid[x][y] == old_value) {
neighbors.push_back(possible_neighbor);
}
}
where using CartesianIndex = std::array<int, 2>;
. The code above is found in the AAA's implementation of Flood Fill in C++. The C++ implementation is equivalent to mine as the size
value is computed in each loop in the exact same way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, the C++ implementation needs an edit.
The thing is, you don't use the size of each line, you use the size of the first line and assume it's the same for each line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, indeed. I blindly copied and pastied that part from the C++ implementation which is also wrong on this case.
I will probably remove it from the loop, but I could probably come up with a way to find in which line of the matrix the given point is situated, in order to get its length. Thanks for your observations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would a simple == check not work here?
Just curious if we could do something like
if (oldValue == newValue) return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it would, but values are floats and my IDE goes crazy because:
"'==' operator is prone to floating point arithmetic errors. Try using 'abs(oldValue, newValue) < ERROR_CONSTANT' instead
"
So, one quick way out is to use the built-in float.Equals(float)
method because if C# is smart enough to correct me on it, it should be smart enough to implement it itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest adding a check to see if you are out of bounds here with your x and y.
This would help prevent IndexOutOfRangeException Errors in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi there 👋
Something else that stood out here is that you are using 2I for naming. It might be good to consider renaming this to be 2D since you are dealing with x and y coordinates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, thanks for the feedback.
I had initially named it "2D" but then i remembered how OpenGL uses "
glVertex2f()
" to refer to a vertex (vector) of 2 floats, "glVertex2i
" to refer to a vertex of 2 integers and "glVertex2d
" to refer to a vertex of 2 doubles. Since we're using points with integers, I usedPoint2I
since according to OpenGL (other libraries do it as well) it would mean "Point of two Integers". I understand that at first glance it's confusing, but now that I have given some context I hope that the logic behind it is clear :)It's simply a convention that I stick to personally.