From 1cc3119d459c75a4b6f1672d8e85b18483411da7 Mon Sep 17 00:00:00 2001 From: Dimitris Yfantidis Date: Fri, 18 Aug 2023 15:38:36 +0300 Subject: [PATCH 1/3] Flood fill in C# --- contents/flood_fill/code/csharp/FloodFill.cs | 187 +++++++++++++++++++ contents/flood_fill/flood_fill.md | 10 + 2 files changed, 197 insertions(+) create mode 100644 contents/flood_fill/code/csharp/FloodFill.cs diff --git a/contents/flood_fill/code/csharp/FloodFill.cs b/contents/flood_fill/code/csharp/FloodFill.cs new file mode 100644 index 000000000..7ebb12b01 --- /dev/null +++ b/contents/flood_fill/code/csharp/FloodFill.cs @@ -0,0 +1,187 @@ +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; + 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() + { + return HashCode.Combine(x, y); + } + } + + // Utility object for comparing List> objects. + private class FloatListEqualityComparer : IEqualityComparer> + { + public bool Equals(List? one, List? two) + { + if (ReferenceEquals(one, two)) + return true; + if (one == null || two == null) + return false; + return one.SequenceEqual(two); + } + + public int GetHashCode(List list) { + return list.GetHashCode(); + } + } + + private static bool InBounds(Point2I size, Point2I loc) + { + if (loc.x < 0 || loc.y < 0) { + return false; + } + return loc.x < size.x && loc.y < size.y; + } + + private static List FindNeighbors(ref List> grid, Point2I loc, float oldValue) + { + var possibleNeighbors = new List + { + new(loc.x, loc.y + 1), + new(loc.x + 1, loc.y), + new(loc.x, loc.y - 1), + new(loc.x - 1, loc.y) + }; + var neighbors = new List(); + + foreach (Point2I possibleNeighbor in possibleNeighbors) + { + var size = new Point2I(grid[0].Count, grid.Count); + var x = possibleNeighbor.x; + var y = possibleNeighbor.y; + if (!InBounds(size, possibleNeighbor)) { + continue; + } + if (grid[x][y].Equals(oldValue)) { + neighbors.Add(possibleNeighbor); + } + } + return neighbors; + } + + private static void RecursiveFill(ref List> grid, Point2I loc, float oldValue, float newValue) + { + if (oldValue.Equals(newValue)) { + return; + } + grid[loc.x][loc.y] = newValue; + + var possibleNeighbors = FindNeighbors(ref grid, loc, oldValue); + foreach (Point2I possibleNeighbor in possibleNeighbors) { + RecursiveFill(ref grid, possibleNeighbor, oldValue, newValue); + } + } + + private static void QueueFill(ref List> grid, Point2I loc, float oldValue, float newValue) + { + if (oldValue.Equals(newValue)) { + return; + } + + var queue = new Queue(); + queue.Enqueue(loc); + grid[loc.x][loc.y] = newValue; + + while (queue.Count > 0) + { + var currentLoc = queue.Dequeue(); + var possibleNeighbors = FindNeighbors(ref grid, currentLoc, oldValue); + foreach (Point2I neighbor in possibleNeighbors) + { + grid[neighbor.x][neighbor.y] = newValue; + queue.Enqueue(neighbor); + } + } + } + + + private static void StackFill(ref List> grid, Point2I loc, float oldValue, float newValue) + { + if (oldValue.Equals(newValue)) { + return; + } + + var stack = new Stack(); + stack.Push(loc); + + while (stack.Count > 0) + { + var currentLoc = stack.Pop(); + + var x = currentLoc.x; + var y = currentLoc.y; + + if (grid[x][y].Equals(oldValue)) + { + grid[x][y] = newValue; + var possibleNeighbors = FindNeighbors(ref grid, currentLoc, oldValue); + foreach (Point2I neighbor in possibleNeighbors) { + stack.Push(neighbor); + } + } + } + } + + + private static void Main(string[] args) + { + // All neighbouring zeros, adjacent to (1, 1), must be replaced with 3 in the end result. + var grid = new List> + { + new(){0, 0, 1, 0, 0}, + new(){0, 0, 1, 0, 0}, + new(){0, 0, 1, 0, 0}, + new(){8, 0, 1, 1, 1}, + new(){0, 0, 0, 0, 0} + }; + var solutionGrid = new List> + { + new(){3, 3, 1, 0, 0}, + new(){3, 3, 1, 0, 0}, + new(){3, 3, 1, 0, 0}, + new(){8, 3, 1, 1, 1}, + new(){3, 3, 3, 3, 3} + }; + var startingPoint = new Point2I(1, 1); + var gridComparator = new FloatListEqualityComparer(); + + var testGrid = new List>(grid); + RecursiveFill(ref testGrid, startingPoint, 0, 3); + Debug.Assert(testGrid.SequenceEqual(solutionGrid, gridComparator), "Recursive Flood Fill Failed"); + + testGrid = new List>(grid); + QueueFill(ref testGrid, startingPoint, 0, 3); + Debug.Assert(testGrid.SequenceEqual(solutionGrid, gridComparator), "Queue Flood Fill Failed"); + + testGrid = new List>(grid); + StackFill(ref testGrid, startingPoint, 0, 3); + Debug.Assert(testGrid.SequenceEqual(solutionGrid, gridComparator), "Stack Flood Fill Failed"); + + } + } +} \ No newline at end of file diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index ed84683a6..432145063 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -96,6 +96,8 @@ In code, this might look like this: [import:10-25, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} [import:15-20, lang="coconut"](code/coconut/flood_fill.coco) +{% sample lang="cs" %} +[import:61-85, lang="csharp"](code/csharp/FloodFill.cs) {% endmethod %} @@ -118,6 +120,8 @@ In code, it might look like this: [import:55-63, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} [import:52-61, lang:"coconut"](code/coconut/flood_fill.coco) +{% sample lang="cs" %} +[import:87-98, lang="csharp"](code/csharp/FloodFill.cs) {% 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. @@ -135,6 +139,8 @@ Additionally, it is possible to do the same type of traversal by managing a stac [import:27-36, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} [import:23-34, lang:"coconut"](code/coconut/flood_fill.coco) +{% sample lang="cs" %} +[import:123-148, lang="csharp"](code/csharp/FloodFill.cs) {% 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: @@ -180,6 +186,8 @@ The code would look something like this: [import:38-53, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} [import:36-49, lang:"coconut"](code/coconut/flood_fill.coco) +{% sample lang="cs" %} +[import:100-120, lang="csharp"](code/csharp/FloodFill.cs) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally. @@ -264,6 +272,8 @@ After, we will fill in the left-hand side of the array to be all ones by choosin [import:, lang="python"](code/python/flood_fill.py) {% sample lang="coco" %} [import, lang="coconut"](code/coconut/flood_fill.coco) +{% sample lang="cs" %} +[import, lang="csharp"](code/csharp/FloodFill.cs) {% endmethod %} From 466ad3780881054415c39b786819d4f6cbacb52a Mon Sep 17 00:00:00 2001 From: Dimitris Yfantidis Date: Sat, 19 Aug 2023 08:01:48 +0300 Subject: [PATCH 2/3] Updated code and .md to fit the new lines --- contents/flood_fill/code/csharp/FloodFill.cs | 75 ++++++++++---------- contents/flood_fill/flood_fill.md | 8 +-- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/contents/flood_fill/code/csharp/FloodFill.cs b/contents/flood_fill/code/csharp/FloodFill.cs index 7ebb12b01..e8e54a673 100644 --- a/contents/flood_fill/code/csharp/FloodFill.cs +++ b/contents/flood_fill/code/csharp/FloodFill.cs @@ -13,30 +13,33 @@ private class Point2I // Coordinates public int x; public int y; - + public Point2I(int x, int y) { this.x = x; this.y = y; } - public override bool Equals(object? o) + 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() + + public override int GetHashCode() { - return HashCode.Combine(x, y); + int hash = 17; + hash = 31 * hash + x; + hash = 31 * hash + y; + return hash; } } - + // Utility object for comparing List> objects. private class FloatListEqualityComparer : IEqualityComparer> { - public bool Equals(List? one, List? two) + public bool Equals(List one, List two) { if (ReferenceEquals(one, two)) return true; @@ -49,8 +52,8 @@ public int GetHashCode(List list) { return list.GetHashCode(); } } - - private static bool InBounds(Point2I size, Point2I loc) + + private static bool InBounds(Point2I size, Point2I loc) { if (loc.x < 0 || loc.y < 0) { return false; @@ -62,10 +65,10 @@ private static List FindNeighbors(ref List> grid, Point2I l { var possibleNeighbors = new List { - new(loc.x, loc.y + 1), - new(loc.x + 1, loc.y), - new(loc.x, loc.y - 1), - new(loc.x - 1, loc.y) + new Point2I(loc.x, loc.y + 1), + new Point2I(loc.x + 1, loc.y), + new Point2I(loc.x, loc.y - 1), + new Point2I(loc.x - 1, loc.y) }; var neighbors = new List(); @@ -83,8 +86,8 @@ private static List FindNeighbors(ref List> grid, Point2I l } return neighbors; } - - private static void RecursiveFill(ref List> grid, Point2I loc, float oldValue, float newValue) + + private static void RecursiveFill(ref List> grid, Point2I loc, float oldValue, float newValue) { if (oldValue.Equals(newValue)) { return; @@ -96,8 +99,8 @@ private static void RecursiveFill(ref List> grid, Point2I loc, float RecursiveFill(ref grid, possibleNeighbor, oldValue, newValue); } } - - private static void QueueFill(ref List> grid, Point2I loc, float oldValue, float newValue) + + private static void QueueFill(ref List> grid, Point2I loc, float oldValue, float newValue) { if (oldValue.Equals(newValue)) { return; @@ -107,20 +110,20 @@ private static void QueueFill(ref List> grid, Point2I loc, float ol queue.Enqueue(loc); grid[loc.x][loc.y] = newValue; - while (queue.Count > 0) + while (queue.Count > 0) { var currentLoc = queue.Dequeue(); var possibleNeighbors = FindNeighbors(ref grid, currentLoc, oldValue); - foreach (Point2I neighbor in possibleNeighbors) + foreach (Point2I neighbor in possibleNeighbors) { grid[neighbor.x][neighbor.y] = newValue; queue.Enqueue(neighbor); } } } - - - private static void StackFill(ref List> grid, Point2I loc, float oldValue, float newValue) + + + private static void StackFill(ref List> grid, Point2I loc, float oldValue, float newValue) { if (oldValue.Equals(newValue)) { return; @@ -129,14 +132,14 @@ private static void StackFill(ref List> grid, Point2I loc, float old var stack = new Stack(); stack.Push(loc); - while (stack.Count > 0) + while (stack.Count > 0) { var currentLoc = stack.Pop(); var x = currentLoc.x; var y = currentLoc.y; - if (grid[x][y].Equals(oldValue)) + if (grid[x][y].Equals(oldValue)) { grid[x][y] = newValue; var possibleNeighbors = FindNeighbors(ref grid, currentLoc, oldValue); @@ -153,19 +156,19 @@ private static void Main(string[] args) // All neighbouring zeros, adjacent to (1, 1), must be replaced with 3 in the end result. var grid = new List> { - new(){0, 0, 1, 0, 0}, - new(){0, 0, 1, 0, 0}, - new(){0, 0, 1, 0, 0}, - new(){8, 0, 1, 1, 1}, - new(){0, 0, 0, 0, 0} + new List(){0, 0, 1, 0, 0}, + new List(){0, 0, 1, 0, 0}, + new List(){0, 0, 1, 0, 0}, + new List(){8, 0, 1, 1, 1}, + new List(){0, 0, 0, 0, 0} }; var solutionGrid = new List> { - new(){3, 3, 1, 0, 0}, - new(){3, 3, 1, 0, 0}, - new(){3, 3, 1, 0, 0}, - new(){8, 3, 1, 1, 1}, - new(){3, 3, 3, 3, 3} + new List(){3, 3, 1, 0, 0}, + new List(){3, 3, 1, 0, 0}, + new List(){3, 3, 1, 0, 0}, + new List(){8, 3, 1, 1, 1}, + new List(){3, 3, 3, 3, 3} }; var startingPoint = new Point2I(1, 1); var gridComparator = new FloatListEqualityComparer(); @@ -173,7 +176,7 @@ private static void Main(string[] args) var testGrid = new List>(grid); RecursiveFill(ref testGrid, startingPoint, 0, 3); Debug.Assert(testGrid.SequenceEqual(solutionGrid, gridComparator), "Recursive Flood Fill Failed"); - + testGrid = new List>(grid); QueueFill(ref testGrid, startingPoint, 0, 3); Debug.Assert(testGrid.SequenceEqual(solutionGrid, gridComparator), "Queue Flood Fill Failed"); @@ -184,4 +187,4 @@ private static void Main(string[] args) } } -} \ No newline at end of file +} diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index 432145063..fab3859ff 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -97,7 +97,7 @@ In code, this might look like this: {% sample lang="coco" %} [import:15-20, lang="coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:61-85, lang="csharp"](code/csharp/FloodFill.cs) +[import:64-88, lang="csharp"](code/csharp/FloodFill.cs) {% endmethod %} @@ -121,7 +121,7 @@ In code, it might look like this: {% sample lang="coco" %} [import:52-61, lang:"coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:87-98, lang="csharp"](code/csharp/FloodFill.cs) +[import:90-101, lang="csharp"](code/csharp/FloodFill.cs) {% 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. @@ -140,7 +140,7 @@ Additionally, it is possible to do the same type of traversal by managing a stac {% sample lang="coco" %} [import:23-34, lang:"coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:123-148, lang="csharp"](code/csharp/FloodFill.cs) +[import:126-151, lang="csharp"](code/csharp/FloodFill.cs) {% 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: @@ -187,7 +187,7 @@ The code would look something like this: {% sample lang="coco" %} [import:36-49, lang:"coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:100-120, lang="csharp"](code/csharp/FloodFill.cs) +[import:103-123, lang="csharp"](code/csharp/FloodFill.cs) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally. From 607e1f5b78c067557ec9a2f5aaa5befa7e29ad9c Mon Sep 17 00:00:00 2001 From: Dimitris Yfantidis Date: Wed, 23 Aug 2023 11:11:40 +0300 Subject: [PATCH 3/3] Refactored Code and Markdown * Removed `Point2I.Equals()` * Removed `Point2I.GetHashCode()`. * Optimized/Shortened `FloatListEqualityComparer.Equals()` * Optimized/Shortened `InBounds()` * Optimized `FindNeighbors()` (moved `size` out of loop). * Refactored `flood_fill.md` to fit the changes. --- contents/flood_fill/code/csharp/FloodFill.cs | 34 ++++---------------- contents/flood_fill/flood_fill.md | 8 ++--- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/contents/flood_fill/code/csharp/FloodFill.cs b/contents/flood_fill/code/csharp/FloodFill.cs index e8e54a673..1d9adcfad 100644 --- a/contents/flood_fill/code/csharp/FloodFill.cs +++ b/contents/flood_fill/code/csharp/FloodFill.cs @@ -19,21 +19,6 @@ 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> objects. @@ -41,24 +26,18 @@ private class FloatListEqualityComparer : IEqualityComparer> { public bool Equals(List one, List two) { - if (ReferenceEquals(one, two)) - return true; - if (one == null || two == null) - return false; - return one.SequenceEqual(two); + return ReferenceEquals(one, two) || one != null && two != null && one.SequenceEqual(two); } - public int GetHashCode(List list) { + public int GetHashCode(List list) + { return list.GetHashCode(); } } private static bool InBounds(Point2I size, Point2I loc) { - 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; } private static List FindNeighbors(ref List> grid, Point2I loc, float oldValue) @@ -71,10 +50,10 @@ private static List FindNeighbors(ref List> grid, Point2I l new Point2I(loc.x - 1, loc.y) }; var neighbors = new List(); + var size = new Point2I(grid[0].Count, grid.Count); foreach (Point2I possibleNeighbor in possibleNeighbors) { - var size = new Point2I(grid[0].Count, grid.Count); var x = possibleNeighbor.x; var y = possibleNeighbor.y; if (!InBounds(size, possibleNeighbor)) { @@ -100,7 +79,7 @@ private static void RecursiveFill(ref List> grid, Point2I loc, float } } - private static void QueueFill(ref List> grid, Point2I loc, float oldValue, float newValue) + private static void QueueFill(ref List> grid, Point2I loc, float oldValue, float newValue) { if (oldValue.Equals(newValue)) { return; @@ -122,7 +101,6 @@ private static void QueueFill(ref List> grid, Point2I loc, float ol } } - private static void StackFill(ref List> grid, Point2I loc, float oldValue, float newValue) { if (oldValue.Equals(newValue)) { diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index fab3859ff..7d6aad813 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -97,7 +97,7 @@ In code, this might look like this: {% sample lang="coco" %} [import:15-20, lang="coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:64-88, lang="csharp"](code/csharp/FloodFill.cs) +[import:43-67, lang="csharp"](code/csharp/FloodFill.cs) {% endmethod %} @@ -121,7 +121,7 @@ In code, it might look like this: {% sample lang="coco" %} [import:52-61, lang:"coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:90-101, lang="csharp"](code/csharp/FloodFill.cs) +[import:69-80, lang="csharp"](code/csharp/FloodFill.cs) {% 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. @@ -140,7 +140,7 @@ Additionally, it is possible to do the same type of traversal by managing a stac {% sample lang="coco" %} [import:23-34, lang:"coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:126-151, lang="csharp"](code/csharp/FloodFill.cs) +[import:104-129, lang="csharp"](code/csharp/FloodFill.cs) {% 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: @@ -187,7 +187,7 @@ The code would look something like this: {% sample lang="coco" %} [import:36-49, lang:"coconut"](code/coconut/flood_fill.coco) {% sample lang="cs" %} -[import:103-123, lang="csharp"](code/csharp/FloodFill.cs) +[import:82-102, lang="csharp"](code/csharp/FloodFill.cs) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally.