Rotating a matrix by 90 degrees

I'm going through books and various questions to improve my algorithm knowledge. I solved each NxN matrix first by hand and applied that process to build the code from that. I have done my best to break each task into its own step and keep them encapsulated. Below is the class followed by its accompanying unit tests. Having finished most of a unit testing book I also followed Test Driven Design (TDD) to write the tests before implementing the logic so I would know I'd achieved the correct answer.

Is there anything I can improve on my logic or naming to make readability clearer?

public class Question7
{
public void RotateImage(ref uint[,] img, RotationDirection direction)
{
int elementsPerRow = ElementsPerRow(img);
int inflectionPoint = (elementsPerRow - 1) / 2;

for (int rowIndex = 0; rowIndex <= inflectionPoint; rowIndex++)
{
int lastColumnIndexToEdit = elementsPerRow - 1 - rowIndex;
for (int columnIndex = rowIndex; columnIndex < lastColumnIndexToEdit; columnIndex++)
{
if (direction == RotationDirection.Clockwise)
{
RotateClockwise(ref img, new Indices(rowIndex, columnIndex), elementsPerRow);
}
else
{
RotateCounterClockwise(ref img, new Indices(rowIndex, columnIndex), elementsPerRow);
}
}
}
}

private int ElementsPerRow(uint[,] img)
{
for (int i = 1; i * i <= img.Length; i++)
{
if (i * i == img.Length)
{
return i;
}
}

return -1;
}

private void RotateClockwise(ref uint[,] img, Indices topLeft, int elementsPerRow)
{
int lastIndex = elementsPerRow - 1;

Indices topRight = new Indices(topLeft.Column, lastIndex - topLeft.Row);
Indices botRight = new Indices(lastIndex - topLeft.Row, lastIndex - topLeft.Column);
Indices botLeft = new Indices(lastIndex - topLeft.Column, topLeft.Row);

uint toInsert = img[topLeft.Row, topLeft.Column];

//Insert into TopRight
uint justStored = img[topRight.Row, topRight.Column];
img[topRight.Row, topRight.Column] = toInsert;
toInsert = justStored;

//Insert into BotRight
justStored = img[botRight.Row, botRight.Column];
img[botRight.Row, botRight.Column] = toInsert;
toInsert = justStored;

//Insert into BotLeft
justStored = img[botLeft.Row, botLeft.Column];
img[botLeft.Row, botLeft.Column] = toInsert;
toInsert = justStored;

//Insert into TopLeft
img[topLeft.Row, topLeft.Column] = toInsert;
}

private void RotateCounterClockwise(ref uint[,] img, Indices topLeft, int elementsPerRow)
{
int lastIndex = elementsPerRow - 1;

Indices topRight = new Indices(topLeft.Column, lastIndex - topLeft.Row);
Indices botRight = new Indices(lastIndex - topLeft.Row, lastIndex - topLeft.Column);
Indices botLeft = new Indices(lastIndex - topLeft.Column, topLeft.Row);

uint toInsert = img[topLeft.Row, topLeft.Column];

//Insert into TopRight
uint justStored = img[botLeft.Row, botLeft.Column];
img[botLeft.Row, botLeft.Column] = toInsert;
toInsert = justStored;

//Insert into BotRight
justStored = img[botRight.Row, botRight.Column];
img[botRight.Row, botRight.Column] = toInsert;
toInsert = justStored;

//Insert into BotLeft
justStored = img[topRight.Row, topRight.Column];
img[topRight.Row, topRight.Column] = toInsert;
toInsert = justStored;

//Insert into TopLeft
img[topLeft.Row, topLeft.Column] = toInsert;
}
}

struct Indices
{
public int Row { get; }
public int Column { get; }

public Indices(int rowIndex, int columnIndex)
{
Row = rowIndex;
Column = columnIndex;
}
}

public enum RotationDirection
{
Clockwise = 0,
CounterClockwise = 1
}


I'm including the 4 most helpful tests to ensure I was producing correct results.

[Fact]
public void A_2x2_matrix_rotated_clockwise()
{
uint[,] expected = new uint[2, 2]{ { 30, 10 }, { 40, 20 } };

uint[,] img = new uint[2, 2] { { 10, 20 }, { 30, 40 } };
var sut = new Question7();

sut.RotateImage(ref img, RotationDirection.Clockwise);

Assert.Equal(expected, img);
}

[Fact]
public void A_2x2_matrix_rotated_counterclockwise()
{
uint[,] expected = new uint[2, 2] { { 20, 40 }, { 10, 30 } };

uint[,] img = new uint[2, 2] { { 10, 20 }, { 30, 40 } };
var sut = new Question7();

sut.RotateImage(ref img, RotationDirection.CounterClockwise);

Assert.Equal(expected, img);
}

[Fact]
public void A_5x5_matrix_rotated_clockwise()
{
uint[,] expected = new uint[5, 5] { { 210, 160, 110, 60, 10 },
{ 220, 170, 120, 70, 20 },
{ 230, 180, 130, 80, 30 },
{ 240, 190, 140, 90, 40 },
{ 250, 200, 150, 100, 50 } };

uint[,] img = new uint[5, 5] { { 10, 20, 30, 40, 50},
{ 60, 70, 80, 90, 100 },
{ 110, 120, 130, 140, 150 },
{ 160, 170, 180, 190, 200 },
{ 210, 220, 230 , 240, 250 } };
var sut = new Question7();

sut.RotateImage(ref img, RotationDirection.Clockwise);

Assert.Equal(expected, img);
}

[Fact]
public void A_5x5_matrix_rotated_counterclockwise()
{
uint[,] expected = new uint[5, 5] { { 50, 100, 150, 200, 250 },
{ 40, 90, 140, 190, 240 },
{ 30, 80, 130, 180, 230 },
{ 20, 70, 120, 170, 220 },
{ 10, 60, 110, 160, 210 } };

uint[,] img = new uint[5, 5] { { 10, 20, 30, 40, 50},
{ 60, 70, 80, 90, 100 },
{ 110, 120, 130, 140, 150 },
{ 160, 170, 180, 190, 200 },
{ 210, 220, 230 , 240, 250 } };
var sut = new Question7();

sut.RotateImage(ref img, RotationDirection.CounterClockwise);

Assert.Equal(expected, img);
}


Once confident in the rotation, I continued and implemented a method to generate an NxN matrix.

public uint[,] GenererateNxNMatrix(int N, uint startValue, uint stepValue)
{
var img = new uint[N, N];
int row = 0;
int column = 0;
int counter = 0;
int totalElements = N * N;

for (uint i = startValue; counter < totalElements; i+= stepValue)
{
img[row, column] = i;

counter++;

if (counter % N == 0)
{
column = 0;
row++;
}
else
{
column++;
}
}

return img;
}


Along its accompanying unit test.

[Fact]
public void Generate_3x3_matrix()
{
var expected = new uint[,] { { 1, 3, 5 }, { 7, 9, 11 }, { 13, 15, 17 } };
var sut = new Question7();

var actual = sut.GenererateNxNMatrix(3, 1, 2);

Assert.Equal(expected, actual);
}


Lastly I created method to generate a matrix that is already rotated.

public uint[,] GenerateRotatedNxNMatrix(int N, RotationDirection rotationDirection, uint startValue, uint stepValue)
{
if (rotationDirection == RotationDirection.Clockwise)
{
return GenerateClockwiseRotatedNxNMatrix(N, startValue, stepValue);
}
else
{
return GenerateCounterClockwiseRotatedNxNMatrix(N, startValue, stepValue);
}
}

public uint[,] GenerateClockwiseRotatedNxNMatrix(int N, uint startValue, uint stepValue)
{
int row = 0;
int column = N - 1;

uint[,] img = new uint[N, N];

for (uint i = startValue; i <= N * N; i += stepValue)
{
img[row, column] = i;

if (i % N == 0)
{
row = 0;
column--;
}
else
{
row++;
}
}

return img;
}

public uint[,] GenerateCounterClockwiseRotatedNxNMatrix(int N, uint startValue, uint stepValue)
{
int row = N - 1;
int column = 0;

uint[,] img = new uint[N, N];

for (uint i = startValue; i <= N * N; i += stepValue)
{
img[row, column] = i;

if (i % N == 0)
{
row = N - 1;
column++;
}
else
{
row--;
}
}

return img;
}


With its accompanying unit tests.

[Fact]
public void Generate_3x3_matrix_rotated_clockwise()
{
var expected = new uint[,] { { 7, 4, 1 }, { 8, 5, 2 }, { 9, 6, 3 } };
var sut = new Question7();

var actual = sut.GenerateRotatedNxNMatrix(3, RotationDirection.Clockwise, 1, 1);

Assert.Equal(expected, actual);
}

[Fact]
public void Generate_3x3_matrix_rotated_counterclockwise()
{
var expected = new uint[,] { { 3, 6, 9 }, { 2, 5, 8 }, { 1, 4, 7 } };
var sut = new Question7();

var actual = sut.GenerateRotatedNxNMatrix(3, RotationDirection.CounterClockwise, 1, 1);

Assert.Equal(expected, actual);
}

[Theory]
[InlineData(3, RotationDirection.Clockwise, 1, 1)]
[InlineData(7, RotationDirection.Clockwise, 1, 1)]
[InlineData(99, RotationDirection.Clockwise, 1, 1)]
public void Rotated_matrix_matches_matrix_that_was_generated_already_rotated(int N, RotationDirection rotationDirection, uint startValue, uint stepValue)
{
var expected = new Question7().GenerateRotatedNxNMatrix(N, rotationDirection, startValue, stepValue);
var img = new Question7().GenererateNxNMatrix(N, startValue, stepValue);
var sut = new Question7();

sut.RotateImage(ref img, rotationDirection);

Assert.Equal(expected, img);
}
$$$$

• (Anybody want to comment on Intels's ipp(i)Transpose`?) – greybeard Jun 13 '20 at 6:09