Skip to content
CCC Python Course

Grid transformations

Module
M4.7
Lesson
1 of 1
Reading time
5 min

In this lesson

  • Rotate a grid 90 degrees clockwise or counterclockwise.
  • Reflect a grid horizontally or vertically.
  • Transpose a grid and understand its relationship to other transformations.
  • Combine multiple transformations to solve symmetry problems.

Grid problems often ask you to rotate, flip or mirror a pattern. Getting the index arithmetic right by hand, cell by cell, invites off-by-one errors. Building each transformation from a couple of simple moves is faster and much harder to get wrong.

The simplest of those moves is the transpose: swap rows and columns, so the cell at row i, column j moves to row j, column i. A grid with 3 rows and 5 columns becomes a grid with 5 rows and 3 columns.

Transpose

Take a 2×3 grid:

A B CD E F

To transpose it, build a new grid with the dimensions swapped, and read column j of the original as row j of the result:

A DB EC F
examples/transpose.py
def transpose(grid):    R = len(grid)    C = len(grid[0])    result = [[grid[i][j] for i in range(R)] for j in range(C)]    return result

grid = [['A', 'B', 'C'], ['D', 'E', 'F']]result = transpose(grid)for row in result:    print(' '.join(row))

Output

A D
B E
C F
Transposing a grid by swapping rows and columns

Rotate 90 degrees clockwise

A 90-degree clockwise rotation sends the top row to the rightmost column and the leftmost column to the top row. You can build it in two steps you already have: transpose the grid, then reverse each row.

Start with:

1 2 34 5 6

Transpose first:

1 42 53 6

Then reverse each row:

4 15 26 3
examples/rotate_clockwise.py
def rotate_clockwise(grid):    R = len(grid)    C = len(grid[0])    transposed = [[grid[i][j] for i in range(R)] for j in range(C)]    result = [row[::-1] for row in transposed]    return result

grid = [[1, 2, 3], [4, 5, 6]]result = rotate_clockwise(grid)for row in result:    print(' '.join(map(str, row)))

Output

4 1
5 2
6 3
Rotating a grid 90 degrees clockwise

Check it against the shape of the answer: the original top-left corner, value 1, is now in the top-right corner of the rotated grid, at row 0, column 1. That is exactly what a clockwise turn should do to a corner.

Rotate 90 degrees counterclockwise

Counterclockwise rotation swaps the second step: transpose, then reverse the order of the rows instead of reversing within each row.

Transpose the same grid:

1 42 53 6

Then reverse the row order:

3 62 51 4
examples/rotate_counterclockwise.py
def rotate_counterclockwise(grid):    R = len(grid)    C = len(grid[0])    transposed = [[grid[i][j] for i in range(R)] for j in range(C)]    result = transposed[::-1]    return result

grid = [[1, 2, 3], [4, 5, 6]]result = rotate_counterclockwise(grid)for row in result:    print(' '.join(map(str, row)))

Output

3 6
2 5
1 4
Rotating a grid 90 degrees counterclockwise

The clockwise and counterclockwise steps use the same transpose, and only differ in which direction you reverse. That is worth holding onto: it is the easiest way to tell the two apart when you are writing the code from memory.

Horizontal and vertical reflection

Reflecting horizontally mirrors left to right. Reverse each row:

A B C       C B AD E F  -->  F E D

Reflecting vertically mirrors top to bottom. Reverse the order of the rows:

A B C       D E FD E F  -->  A B C
examples/reflect.py
def reflect_horizontal(grid):    return [row[::-1] for row in grid]

def reflect_vertical(grid):    return grid[::-1]

grid = [['A', 'B', 'C'], ['D', 'E', 'F']]
print("Original:")for row in grid:    print(' '.join(row))
print("\nHorizontal reflection:")h_result = reflect_horizontal(grid)for row in h_result:    print(' '.join(row))
print("\nVertical reflection:")v_result = reflect_vertical(grid)for row in v_result:    print(' '.join(row))

Output

Original:
A B C
D E F

Horizontal reflection:
C B A
F E D

Vertical reflection:
D E F
A B C
Reflecting grids horizontally and vertically

Why these transformations work

Each transformation is a permutation of positions, and each one has a natural inverse. Transposing moves (i, j) to (j, i), and doing that twice puts every cell back where it started. Reflecting left to right twice, or top to bottom twice, does the same. A 90-degree rotation is different: applying it four times, not two, returns to the start, since one turn is a quarter of a full circle.

All four transformations run in O(R × C) time, since each one visits every cell once to build the new grid. You could reverse rows or columns in place to save memory, but building a fresh grid is simpler to get right, and for grid sizes in a contest, the extra memory rarely matters.

Combining transformations

Problems often chain these moves together. A 180-degree rotation is two 90-degree rotations in the same direction. A reflection followed by a rotation is not the same as a rotation followed by that reflection, so the order you apply them in changes the result.

One pattern worth knowing: to check whether two grids are "the same shape" under rotation and reflection, generate all eight versions of one grid (four rotations, and the same four rotations after a reflection), and see whether any of them equals the other grid. This comes up whenever a problem treats a pattern and its mirror image or rotation as equivalent.

Whenever you combine transformations, track the grid's dimensions as you go. Every 90-degree rotation swaps the row and column counts, so a grid that was R×C becomes C×R after one rotation and R×C again after the next.

Getting the details right

The dimension swap is the detail most solutions miss. A rotated R×C grid becomes C×R, and if R and C differ, allocating a result grid with the original dimensions produces an index-out-of-bounds error the moment you write past its shorter side.

Clockwise and counterclockwise are also easy to swap by accident, since the code for both starts with the same transpose. Track one corner through the transformation to check which one you wrote: on the 2×3 example above, a clockwise turn puts the original top-left value in the new top-right corner, while a counterclockwise turn puts it in the new bottom-left corner instead.

If you reverse rows or the row order in place rather than building a new grid, swap position i with position R - 1 - i and stop once you reach the midpoint, or you will undo every swap you already made. Building a new grid sidesteps this bug entirely, which is why it is the safer default unless memory is tight.

Diagonal reflections

Two more reflections come up less often but are worth recognizing: reflecting across the main diagonal (top-left to bottom-right) and across the anti-diagonal (top-right to bottom-left). The main-diagonal reflection sends (i, j) to (j, i), which is exactly the transpose from earlier in this lesson; transpose and "reflect across the main diagonal" are two names for the same operation. The anti-diagonal reflection sends (i, j) to (C - 1 - j, R - 1 - i), and you can build it from moves you already have: transpose the grid, then reflect the result both horizontally and vertically (equivalently, rotate 180 degrees after transposing).

Knowing that both diagonal reflections reduce to a transpose plus a plain reflection means you never need to derive a fifth index formula from scratch. Every grid symmetry in this lesson decomposes into the same two primitives: swap rows and columns, or reverse an axis. That is the real reason to learn transpose and reflect first: everything else in the lesson is one or two of those, composed.

Practice

Try this on the judge. The link opens the problem on WMOJ.

  1. 2021 J2
    Silent Auction (opens on WMOJ in a new tab) WMOJ

    Rotate a grid pattern to find its appearance after applying turns.