Avoid if statements for checking neighboring indexes in a 2D array

Ivan Kazmenko via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sun Jul 16 05:47:14 PDT 2017


On Sunday, 16 July 2017 at 10:37:39 UTC, kerdemdemir wrote:
> My goal is to find connected components in a 2D array for 
> example finding connected '*'
> chars below.
>
>       x x x x x x
>       x x x x x x
>       x x * * x x
>       x x * * x x
>       x x x * * x
>       * x x x x x

> ...

> Is there any better way to achieve this with cool std functions 
> like enumerate or iota without needing to write eight if checks?

I don't know of a library facility to do this.

Still, there is a language-agnostic way to make it more concise.  
Instead of repeating eight similar blocks, define an array of 
delta-rows and delta-columns to neighboring cells, and use that 
array in a loop.  A complete example follows:

-----
import std.algorithm, std.array, std.range, std.stdio;

immutable int dirs = 8;
immutable int [dirs] dRow = [-1, -1, -1,  0, +1, +1, +1,  0];
immutable int [dirs] dCol = [-1,  0, +1, +1, +1,  0, -1, -1];

char [] [] arr;

int componentSizeRecur (int row, int col)
{
	int res = 1;
	arr[row][col] = 'x';
	foreach (dir; 0..dirs)
	{
		auto nRow = row + dRow[dir];
		auto nCol = col + dCol[dir];
		if (arr[nRow][nCol] == '*')
			res += componentSizeRecur (nRow, nCol);
	}
	return res;
}

void main ()
{
	arr = ["xxxxxxx",
	       "xxxx*xx",
	       "xx**xxx",
	       "xx**x*x",
	       "xxxxxxx",
	       ].map !(line => line.dup).array;

	foreach (row; 0..arr.length)
		foreach (col; 0..arr.front.length)
			if (arr[row][col] == '*')
				writeln (componentSizeRecur (row, col));
}
-----

If the neighbors array is regular and known in advance (like, 4 
edge-connected cells, or 8 corner-connected cells as here), you 
may also like to loop over possible deltas and pick the good 
ones, like below:

-----
int componentSizeRecur (int row, int col)
{
	int res = 1;
	arr[row][col] = 'x';
	foreach (dRow; -1..+2)
		foreach (dCol; -1..+2)
			if (dRow || dCol)
			{
				auto nRow = row + dRow;
				auto nCol = col + dCol;
				if (arr[nRow][nCol] == '*')
					res += componentSizeRecur (nRow, nCol);
			}
	return res;
}
-----

I have to make two additional notes.

1. This works only if the border does not contain '*' characters. 
  To make it work without that restriction, either add two 
sentinel rows and columns at the four borders of the array, or 
put an if on nRow and nCol before using them.

2. The recursive solution can eat up lots of stack.  If you 
intend using it on large arrays, make sure you don't hit the 
stack size limit of the environment.  On Windows, it can be 
achieved by a compiler switch like "-L/STACK:268435456".  On 
Linux, the "ulimit" command may help.

Ivan Kazmenko.



More information about the Digitalmars-d-learn mailing list