2

I have a squared 2D array that I would like to rotate clockwise and counter clockwise.

I was following this answer here for rotating to the right:

Rotating a 2D pixel array by 90 degrees

The code I developed:

void rotateRight()
{  
    for (int i = 0; i < m_width; i += 1) {
        for (int j = i + 1; j < m_height; j += 1) {
            std::swap(get(i, j), get(j, i));  
        }
    }
}

However, the array did not rotate. I have a 10x10 array with 5's, and on the top-left corner is a 7. I expected the 7 to go to the top-right corner after rotating, but it's still at the top-left corner.

The member function get() is my own, which just returns a reference to a cell.

T& get(const int x, const int y)
{ 
    return m_array[y * m_width + x];
} 

How can I get this to rotate? Does a new array have to be made? Would appreciate the help. Thanks!

Update:

Latest attempt. Okay, so the '7' rotated to the right finally from the top-left corner to the top-right corner. But when rotateRight() is called again, it fails. My 7 on the top-right corner is gone and no longer found. Looking into it.

for (int i = 0; i < m_width; i += 1) {
    for (int j = i + 1; j < m_height; j += 1) {
        get(j, i) = get(i, j);
    }
}
for (int i = 0; i < m_height; i++) {
    for (int j = 0, k = m_height - 1; j<k; j++, k--) {
        std::swap(get(j, i), get(k, i));
    }
}

Output:

Original:
700
000
000

Rotation #1:
J: 1, I: 0
J: 2, I: 0
J: 2, I: 1
J: 0, I: 0, K: 2
Value: 7 J: 0 K: 2
J: 0, I: 1, K: 2
Value: 0 J: 0 K: 2
J: 0, I: 2, K: 2
Value: 0 J: 0 K: 2
007
000
000

Rotation #2:
J: 1, I: 0
J: 2, I: 0
J: 2, I: 1
J: 0, I: 0, K: 2
Value: 0 J: 0 K: 2
J: 0, I: 1, K: 2
Value: 0 J: 0 K: 2
J: 0, I: 2, K: 2
Value: 0 J: 0 K: 2
000
000
000

Final code

// Rotates a squared array clockwise.
void rotateRight()
{         
    T temp; 
    int halfwidth_floor = m_width / 2;
    int halfwidth_ceil = (m_width + 1) / 2; 

    for (int j = 0; j < halfwidth_floor; j += 1) {
        for (int i = 0; i < halfwidth_ceil; i += 1) {
            std::swap(temp, get(i, j)); 
            std::swap(get(i, j), get(j, m_width - i - 1));
            std::swap(get(j, m_width - i - 1), get(m_width - i - 1, m_width - j - 1));
            std::swap(get(m_width - i - 1, m_width - j - 1), get(m_width - j - 1, i));
            std::swap(get(m_width - j - 1, i), temp);
        }
    } 
}   

// Rotates a squared array counter-clockwise.
void rotateLeft()
{
    T temp; 
    int n = m_width;  

    for (int i = 0;  i < n / 2; i++) {
        for (int j = i; j < n - i - 1; j++) {
            std::swap(temp, get(i, j));  
            std::swap(get(i, j), get(n - j - 1, i));
            std::swap(get(n - j - 1, i), get(n - i - 1, n - j - 1));
            std::swap(get(n - i - 1, n - j - 1), get(j, n - i - 1));
            std::swap(get(j, n - i - 1), temp);
        }
    }
} 
Phil C
  • 139
  • 1
  • 11

2 Answers2

4

Each iteration of your inner loop is swapping (i, j) with (j, i).

If (i == j), then it will do nothing.

If (i != j), then that swap will be done twice - for example, the swap done when i == 3 and j == 4 will be done again when i == 4 and j == 3. Doing a swap twice of course results in nothing happening overall.

That's why your code will finish with an array that's exactly the same as it started.

What you may want to do instead is to write into a different array to the one you started with.

edit: nevermind, I missed the int j = i + 1 part of the question and thought both loops were iterating from 0 to the width or height. (The above might still be a useful insight for anyone looking to do the same thing though.)

Your code will transpose, not rotate, the array. (Transpose means to reflect along the diagonal from top left to bottom right.) To rotate, you'd want to move each element (i, j) to (j, w-i-1) or to (h-j-1, i). (Though of course, to rotate by 90 degrees, your array needs to be a square, i.e. (w == h)).

edit 2:

If you want to rotate a square array in-place, by 90 degrees, std::swap won't work (because it swaps 2 elements), and raw assignment won't work (because it loses information). What you can do, though, is to iterate over a quarter of the array and rotate a sequence of 4 elements, like so:

void rotateRight()
{
    int halfwidth_floor = m_width / 2;
    int halfwidth_ceil = (m_width + 1) / 2;
    for (int j = 0; j < halfwidth_floor; j += 1) {
        for (int i = 0; i < halfwidth_ceil; i += 1) {
            value_type temp = get(i, j);
            get(i, j) = get(j, m_width-i-1);
            get(j, m_width-i-1) = get(m_width-i-1, m_width-j-1);
            get(m_width-i-1, m_width-j-1) = get(m_width-j-1, i);
            get(m_width-j-1, i) = temp;
        }
    }
}

Where value_type is the type of your array elements (and m_width == m_height).

On the first iteration, this will move the bottom left to the top left, then the bottom right to the bottom left, then the top right to the bottom right, then the (temporarily saved) top left to the top right. Subsequent iterations will do the same for other points, getting closer towards the center. Note that one for loop iterates to half the size rounded down, and the other rounds up: this is so that in an array with an odd size (e.g. 5x5) it will look at a rectangle of 2x3 and its "rotations" around the square, to avoid rotating the middle elements twice (or not at all).

Keiji
  • 880
  • 5
  • 12
  • m_size is m_width * m_height. Putting size into get(w,h) causes an exception. Should I just used one of the square sizes of width or height? – Phil C Mar 20 '18 at 23:26
  • Sorry about that, my mistake. You're dealing with a square array so when I wrote m_size, I actually meant m_width (or equivalently m_height). I've edited my answer. – Keiji Mar 20 '18 at 23:38
  • 1
    First of all, this works well, but the bitwise copy into temp is troublesome to me. This seems similar to the problem I posted above with similar code logic. What happens with a temp variable is it does a bitwise copy, which can be expensive. Get() is returning a reference, but temp doesn't get assigned to a pointer. I'm hoping to use a pointer to avoid heavy bitwise copying. Is there a way with temp? If I try a pointer now, it doesn't rotate and the output is all 0's. I know why, but I'm trying to find an alternative so it can avoid bitwise copying. I have getWithPointer(x, y) as well. – Phil C Mar 21 '18 at 02:44
  • It does seem like a bitwise copy to temp is standard though, based on other examples I see on the web. But this gets me in the right direction. I appreciate taking the time, and to all! – Phil C Mar 21 '18 at 03:11
  • If you want to avoid copies in the 5 assignments in the loop, you can use std::swap instead of each assignment (e.g. std::swap(temp, get(i, j)) etc.), or use move assignment (temp = std::move(get(i, j))). – Keiji Mar 21 '18 at 08:48
  • I posted my final code above using std::swap. Is this what you had in mind? I believe this gets rid of the bitwise copying because of the references std::swap uses. – Phil C Mar 21 '18 at 21:41
  • 1
    Yes -- exactly. – Keiji Mar 22 '18 at 09:07
0

What you are doing Transposing the matrix.

What you should be doing The algorithm to rotate a matrix consists of 2 steps

  1. Transpose the matrix ( if rotating left) . Transpose and reverse the order of the columns if rotating right)
  2. Reverse the columns

Illustrated below

    1 2 3        1 4 7      3 6 9
    4 5 6    =>  2 5 8  =>  2 5 8
    7 8 9        3 6 9      1 4 7

Code Like so, (initialize an aux array that will hold the rotated array, because the shape could be not square)

void rotate()
{  
    for (int i = 0; i < m_width; i += 1) {
        for (int j = i + 1; j < m_height; j += 1) {
            aux_array[j][i] = get(i, j)
        }
    }
    for (int i=0; i < m_height; i++) {
        for (int j=0,  int k=m_height-1; j<k; j++,k--) {
            swap(aux_array[j][i], aux_array[k][i]);
        }
    }
}
Srini
  • 1,619
  • 1
  • 19
  • 34