A Stepwise C# Implementation of a Solution to the ‘Spiral Print’ Problem





4.00/5 (2 votes)
May 4, 2020
5 min read

6833

27
Stepwise implementation of a program to solve one version of the ‘spiral print’ problem
Introduction
The statement of one version of the problem is usually made by means of an example. Given a 4 x 4 array such as:
ABCD
EFGH
IJKL
MNOP
write code to print the array clockwise in spiral form, that is, ABCDHLPONMIEFGKJ
. The other version of the problem is to print it counterclockwise.
From the problem statement, it is clear that using two indices, say i
and j
, to access rows and columns, respectively, there must be a way to restrict their range as the printing progresses. Therefore, the following declarations are in order:
const int n = 4;
static int minI = 0, maxI = n - 1;
static int minJ = 0, maxJ = n - 1;
where the static
variables will constrain the range of values to be taken by the row and column indices i
and j
.
The printing process must continue until all the elements have been printed. Since the program would be expected to work for a square array of any size, the code would be embedded in an infinite loop, to be stopped when some termination condition is met. The first step involves printing the first row of the array, thus leading to the following initial coding of the program.
using System;
using System.Diagnostics;
namespace StepwiseSpiralPrint
{
class Program
{
const int n = 4;
static int minI = 0, maxI = n - 1;
static int minJ = 0, maxJ = n - 1;
static char[ , ]
arr = new char[ n, n ] { { 'A', 'B', 'C', 'D' },
{ 'E', 'F', 'G', 'H' },
{ 'I', 'J', 'K', 'L' },
{ 'M', 'N', 'O', 'P' } };
static void Main( string[] args )
{
int i = minI, j = minJ;
int stepI = 1, stepJ = 1; ;
PrintArray( arr );
Console.WriteLine( "Spiral print of the array:\n" );
while ( true )
{
while ( j <= maxJ )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j > maxJ );
break;
}
Console.WriteLine( "\n" );
}// Main
public static void PrintArray( char[ , ] arr )
{
int n = arr.GetLength( 0 );
Console.WriteLine( "\nArray:\n" );
for ( int i = 0; i < n; ++i )
{
for ( int j = 0; j < n; ++j )
{
Console.Write( "{0} ", arr[ i, j ] );
}
Console.WriteLine();
}
Console.WriteLine();
}// PrintArray
}// Program (class)
}// StepwiseSpiralPrint (namespace)
Variables stepI
and stepJ
in function Main
are needed because index i
can move downward or upward depending on the sign of stepI
, while index j
can move forward or backward depending on the sign of stepJ
. Observe the use of System.Diagnostics.Debug.Assert
, which allows the verification of the state of program variables. Function PrintArray
naturally prints the array to be printed in spiral form, and it makes no assumptions about the dimensions of the array. Execution of the preceding program yields the following output:
Array:
A B C D
E F G H
I J K L
M N O P
Spiral print of the array:
ABCD
Press any key to continue . . .
From now on, only the changes to function Main
will be shown. The next step involves printing the elements in the last column under the last element of the first row. That requires adjusting the value of index j
and a while
loop over index i
, as follows:
static void Main( string[] args )
{
int i = minI, j = minJ;
int stepI = 1, stepJ = 1; ;
PrintArray( arr );
Console.WriteLine( "Spiral print of the array:\n" );
while ( true )
{
while ( j <= maxJ )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j > maxJ );
--maxJ;
--j;
++minI;
i += stepI;
while ( i <= maxI )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i > maxI );
break;
}
Console.WriteLine( "\n" );
}// Main
Observe that maxJ
and minI
have been adjusted in preparation for the next “round
”. Execution of the modified Main
function produces the following output:
Array:
A B C D
E F G H
I J K L
M N O P
Spiral print of the array:
ABCDHLP
Press any key to continue . . .
The next step requires printing the elements of the last row preceding the last element of the array. That involves adjusting the value of index i
and changing the sign of stepJ
so that index j
moves backward.
static void Main( string[] args )
{
int i = minI, j = minJ;
int stepI = 1, stepJ = 1; ;
PrintArray( arr );
Console.WriteLine( "Spiral print of the array:\n" );
while ( true )
{
while ( j <= maxJ )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j > maxJ );
--maxJ;
--j;
++minI;
i += stepI;
while ( i <= maxI )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i > maxI );
--maxI;
--i;
Debug.Assert( i > maxI && j > maxJ );
stepJ = -1;
j += stepJ;
while ( minJ <= j )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j == minJ - 1 );
break;
}
Console.WriteLine( "\n" );
}// Main
Observe, again, that the value of maxI
has been adjusted in preparation for the next round. Execution of the modified Main
function yields the following output:
Array:
A B C D
E F G H
I J K L
M N O P
Spiral print of the array:
ABCDHLPONM
Press any key to continue . . .
The last step in the first round involves printing the elements in the first column between the first element of the last row and the first element of the array. To accomplish this, the value of index j
must be adjusted, and the sign of stepI
must be changed so that index i
moves upward.
static void Main( string[] args )
{
int i = minI, j = minJ;
int stepI = 1, stepJ = 1; ;
PrintArray( arr );
Console.WriteLine( "Spiral print of the array:\n" );
while ( true )
{
while ( j <= maxJ )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j > maxJ );
--maxJ;
--j;
++minI;
i += stepI;
while ( i <= maxI )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i > maxI );
--maxI;
--i;
Debug.Assert( i > maxI && j > maxJ );
stepJ = -1;
j += stepJ;
while ( minJ <= j )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j == minJ - 1 );
++j;
stepI = -1;
i += stepI;
while ( minI <= i )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i == minI - 1 );
break;
}
Console.WriteLine( "\n" );
}// Main
As can be seen from the following output obtained by executing the program, the four steps implemented so far have printed the “outer layer” of the spiral.
Array:
A B C D
E F G H
I J K L
M N O P
Spiral print of the array:
ABCDHLPONMIE
Press any key to continue . . .
Now, before starting the next round, the conditions for terminating the infinite loop must be evaluated. Termination must occur when the “center” of the spiral has been reached.
static void Main( string[] args )
{
int i = minI, j = minJ;
int stepI = 1, stepJ = 1; ;
PrintArray( arr );
Console.WriteLine( "Spiral print of the array:\n" );
while ( true )
{
while ( j <= maxJ )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j > maxJ );
--maxJ;
--j;
++minI;
i += stepI;
while ( i <= maxI )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i > maxI );
--maxI;
--i;
Debug.Assert( i > maxI && j > maxJ );
stepJ = -1;
j += stepJ;
while ( minJ <= j )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j == minJ - 1 );
++j;
stepI = -1;
i += stepI;
while ( minI <= i )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i == minI - 1 );
++i;
++minJ;
if ( minI > maxI || minJ > maxJ )
{
break;
}
else
{
i = minI; j = minJ; stepI = 1; stepJ = 1;
}
}
Console.WriteLine( "\n" );
}// Main
After the last call to Debug.Assert
in function Main
, index i
and the limit minJ
are adjusted in preparation for the next round (if any). The condition for termination occurs when either one of the min limits exceeding its corresponding max limit. If the condition is not satisfied, the indices are set to their lower limits, the step variables are re-initialized, and execution of the main while
loop continues. Since there are no more forced break
statements, the execution of the completed Main
function produces the desired output.
Array:
A B C D
E F G H
I J K L
M N O P
Spiral print of the array:
ABCDHLPONMIEFGKJ
Press any key to continue . . .
Extending the Program
Now that a solution to the problem of clockwise-printing a square array has been obtained, a question arises: what if it is desired to print a non-square array? Furthermore, what if it is also desired to process more than one array? It turns out that, in answer to the first question, the code that was developed for a square array can handle arbitrary two-dimensional arrays without any changes, except for the definition of suitable constants for the two dimensions. In answer to the second question, the code in function Main
above can be re-factored into a function, say SpiralPrintArray
, which would take as an argument the array to be printed, and which would call a function (InitLimits
) to initialize the min
and max
limits before performing its task. Without further ado, the complete program to handle arbitrary two-dimensional arrays is as follows:
using System;
using System.Diagnostics;
namespace SpiralPrint
{
class Program
{
const int n1 = 4, m1 = 4,
n2 = 4, m2 = 5,
n3 = 6, m3 = 5;
static int minI, maxI;
static int minJ, maxJ;
static char[ , ]
arr1 = new char[ n1, m1 ] { { 'A', 'B', 'C', 'D' },
{ 'E', 'F', 'G', 'H' },
{ 'I', 'J', 'K', 'L' },
{ 'M', 'N', 'O', 'P' } },
arr2 = new char[ n2, m2 ] { { 'A', 'B', 'C', 'D', '1' },
{ 'E', 'F', 'G', 'H', '2' },
{ 'I', 'J', 'K', 'L', '3' },
{ 'M', 'N', 'O', 'P', '4' } },
arr3 = new char[ n3, m3 ] { { 'A', 'B', 'C', 'D', '1' },
{ 'E', 'F', 'G', 'H', '2' },
{ 'I', 'J', 'K', 'L', '3' },
{ 'M', 'N', 'O', 'P', '4' },
{ 'u', 'v', 'w', 'x', 'y' },
{ '$', '%', '*', '#', '@' } };
static void Main( string[] args )
{
SpiralPrintArray( arr1 );
SpiralPrintArray( arr2 );
SpiralPrintArray( arr3 );
}// Main
public static void SpiralPrintArray( char[ , ] arr )
{
InitLimits( arr.GetLength( 0 ), arr.GetLength( 1 ) );
int i = minI, j = minJ;
int stepI = 1, stepJ = 1;
PrintArray( arr );
Console.WriteLine( "Spiral print of the array:\n" );
while ( true )
{
while ( j <= maxJ )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j > maxJ );
--maxJ;
--j;
++minI;
i += stepI;
while ( i <= maxI )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i > maxI );
--maxI;
--i;
Debug.Assert( i > maxI && j > maxJ );
stepJ = -1;
j += stepJ;
while ( minJ <= j )
{
Console.Write( arr[ i, j ] );
j += stepJ;
}
Debug.Assert( j == minJ - 1 );
++j;
stepI = -1;
i += stepI;
while ( minI <= i )
{
Console.Write( arr[ i, j ] );
i += stepI;
}
Debug.Assert( i == minI - 1 );
++i;
++minJ;
if ( minI > maxI || minJ > maxJ )
{
break;
}
else
{
i = minI; j = minJ; stepI = 1; stepJ = 1;
}
}
Console.WriteLine( "\n" );
}// SpiralPrintArray
public static void InitLimits( int _n, int _m )
{
minI = 0; maxI = _n - 1;
minJ = 0; maxJ = _m - 1;
}// InitLimits
public static void PrintArray( char[ , ] arr )
{
int n = arr.GetLength( 0 ), m = arr.GetLength( 1 );
Console.WriteLine( "\nArray:\n" );
for ( int i = 0; i < n; ++i )
{
for ( int j = 0; j < m; ++j )
{
Console.Write( "{0} ", arr[ i, j ] );
}
Console.WriteLine();
}
Console.WriteLine();
}// PrintArray
}// Program (class)
}// SpiralPrint (namespace)
The execution of the program produces the following output:
Array:
A B C D
E F G H
I J K L
M N O P
Spiral print of the array:
ABCDHLPONMIEFGKJ
Array:
A B C D 1
E F G H 2
I J K L 3
M N O P 4
Spiral print of the array:
ABCD1234PONMIEFGHLKJ
Array:
A B C D 1
E F G H 2
I J K L 3
M N O P 4
u v w x y
$ % * # @
Spiral print of the array:
ABCD1234y@#*%$uMIEFGHLPxwvNJKO
Press any key to continue . . .
Usage of the Code
The ZIP file contains directories with all the versions (development steps) of the program. To use the code, create a Visual Studio console application and copy the code in the Program.cs file of your choosing. In the IDE, select Build->Build Solution. After the build succeeds, select Debug->Start Without Debugging. A Windows command-prompt window will appear, showing the output produced by the program.
Conclusion
This article has presented the stepwise implementation of a program to solve one version of the ‘spiral print’ problem. The code was written in the spirit of Niklaus Wirth’s principles set forth in his paper “Program Development by Stepwise Refinement” (Communications of the ACM, Vol. 14, Nr. 4, April 1971, pp. 221-227), and illustrates the power of such an approach to structured programming.
History
- 3rd May, 2020: Initial version
License
This article, along with any associated source code and files, is licensed under The MIT License and Code Project Open License (CPOL).