A simple but effective naive implementation would be to do this:
- Find any cell that isn't numbered (any algorithm fits, a simple linear search is the naive implementation)
- Use a flood fill algorithm to fill the area this cell is in, with a new number
- Rinse and repeat until step 1 can't find more unnumbered cells
Here's a simple implementation. Note that I used a int?[,] rectangular array to hold the map, you will have to adapt the code to your needs. I use LINQPad to test this in, so the .Dump() extension method there simply dumps the output of the LINQ query to the output window. Sample output below the code:
void Main()
{
var map = new int?[30, 20];
for (int x = 0; x < 30; x++)
{
map[x, 10 - x / 3] = 0;
map[x, 5 + x / 2] = 0;
map[x, 18 - x / 5] = 0;
}
ShowMap(map);
NumberRegions(map);
ShowMap(map);
}
public static void ShowMap(int?[,] map)
{
Enumerable.Range(0, map.GetLength(1))
.Select(y => string.Join("", Enumerable.Range(0, map.GetLength(0)).Select(x => map[x, y]?.ToString() ?? "?")))
.Dump();
}
public static void NumberRegions(int?[,] map)
{
var stack = new Stack<(int x, int y)>();
int nextNumber = 1;
for (int x = 0; x < map.GetLength(0); x++)
{
for (int y = 0; y < map.GetLength(1); y++)
{
if (map[x, y] is null)
{
stack.Push((x, y));
while (stack.Any())
{
(int cx, int cy) = stack.Pop();
if (map[cx, cy] is null)
{
map[cx, cy] = nextNumber;
if (cx > 0) stack.Push((cx - 1, cy));
if (cx < map.GetLength(0) - 1) stack.Push((cx + 1, cy));
if (cy > 0) stack.Push((cx, cy - 1));
if (cy < map.GetLength(1) - 1) stack.Push((cx, cy + 1));
}
}
nextNumber++;
}
}
}
}
Here is the sample output:
Before:
??????????????????????????????
???????????????????????????000
????????????????????????000???
?????????????????????000??????
??????????????????000?????????
00?????????????000????????????
??00????????000???????????????
????00???000??????????????????
??????000?????????????????????
???000??00????????????????????
000???????00??????????????????
????????????00????????????????
??????????????00??????????????
????????????????00???????00000
??????????????????0000000?????
???????????????0000000????????
??????????00000???????00??????
?????00000??????????????00????
00000?????????????????????00??
????????????????????????????00
After:
111111111111111111111111111111
111111111111111111111111111000
111111111111111111111111000555
111111111111111111111000555555
111111111111111111000555555555
001111111111111000555555555555
220011111111000555555555555555
222200111000555555555555555555
222222000555555555555555555555
222000330055555555555555555555
000333333300555555555555555555
333333333333005555555555555555
333333333333330055555555555555
333333333333333300555555500000
333333333333333333000000066666
333333333333333000000066666666
333333333300000444444400666666
333330000044444444444444006666
000004444444444444444444440066
444444444444444444444444444400
The reason I'm saying it's a naive implementation is that if you have a very large map, the "search for the next unnumbered cell" can potentially be a big deal. If you could find a good way to keep track of 0-borders you've found so that you can hunt for unnumbered cells around those, that might work out, but there's plenty of edge cases to consider here so I didn't bother even trying.
Here's a variant that simply splits the flood fill implementation into a separate method:
public static void NumberRegions(int?[,] map)
{
int nextNumber = 1;
for (int x = 0; x < map.GetLength(0); x++)
for (int y = 0; y < map.GetLength(1); y++)
if (map[x, y] is null)
{
FloodFill(map, x, y, nextNumber);
nextNumber++;
}
}
public static void FloodFill(int?[,] map, int x, int y, int number)
{
var work = new Stack<(int x, int y)>();
work.Push((x, y));
while (work.Any())
{
(x, y) = work.Pop();
if (map[x, y] is not null)
continue;
map[x, y] = number;
if (x > 0) work.Push((x - 1, y));
if (x < map.GetLength(0) - 1) work.Push((x + 1, y));
if (y > 0) work.Push((x, y - 1));
if (y < map.GetLength(1) - 1) work.Push((x, y + 1));
}
}
Since you mention some requirements in your comment, if you cannot use the Stack<> type, here's a recursive version of the FloodFill method above:
public static void FloodFill(int?[,] map, int x, int y, int number)
{
if (map[x, y] is null)
{
map[x, y] = number;
if (x > 0) FloodFill(map, x-1, y, number);
if (x < map.GetLength(0) - 1) FloodFill(map, x+1, y, number);
if (y > 0) FloodFill(map, x, y-1, number);
if (y < map.GetLength(1) - 1) FloodFill(map, x, y+1, number);
}
}