Search code examples
arraysrubyalgorithmmultidimensional-arraytraversal

ruby - rotate Matrix anti-clockwise by n position


Given a 2D matrix:

matrix = [
   [  1,   2,   3,   4  ],
   [  5,   6,   7,   8  ],
   [  9,  10,  11,  12  ],
   [ 13,  14,  15,  16  ]
]

How can we rotate the matrix anti-clockwise so that values are pushed like this?

matrix = [
   [  2,   3,   4,   8  ]
   [  1,   7,  11,  12  ]
   [  5,   6,  10,  16  ]
   [  9,  13,  14,  15  ]
]

Note

This question is not a duplicate of this & this because what I'm trying to achieve is by rotating the values in anti-clockwise fashion.

My current implementation & Problem

My current implementation only prints out the values in anti-clockwise fashion, but it does not rotate the values.

  layers = [_rows, _cols].min / 2
  r1, r2, c3, c4 = 0, _rows, _cols, _cols
  new_matrix = Array.new(_rows + 1) { Array.new(_cols + 1) }
  (0..layers).each do |layer|
    row_top_left, row_bottom_left,  col_top_right, col_bottom_right = r1, r2, c3, c4
    result = []

    while row_top_left < row_bottom_left
      result << matrix[row_top_left][layer]
      row_top_left += 1
    end

    row_bottom_left = layer
    while row_bottom_left < col_bottom_right
      result << matrix[row_top_left][row_bottom_left]
      row_bottom_left += 1
    end

    temp_col_bottom_right = col_bottom_right
    temp_col_top_right = layer
    while col_bottom_right > temp_col_top_right
      result << matrix[col_bottom_right][temp_col_bottom_right]
      col_bottom_right -= 1
    end

    # p row_top_left
    tmp_row_top_left = layer
    while col_top_right > tmp_row_top_left
      result << matrix[tmp_row_top_left][col_top_right]
      col_top_right -= 1
    end
    p result.cycle



    r1 += 1
    r2 -= 1
    c3 -= 1
    c4 -= 1

update v0.1

The key idea is that the matrix needs to be rotated in the correct way. For example, let's say our matrix requires 2 rotation. Therefore:

   matrix_rotation(
       matrix.length - 1,      # rows
       matrix[0].length - 1,   # columns
       2,                      # Nom. of rotation
       matrix                  # The matrix
   )
matrix = [ 
   #   Original               Iter: 1             Iter: 2  
  [ 1,   2,  3,  4 ],  # [ 2,  3,  4,  8 ]  # [ 3,  4,  8, 12 ]
  [ 5,   6,  7,  8 ],  # [ 1,  7, 11, 12 ]  # [ 2, 11, 10, 16 ]
  [ 9,  10, 11, 12 ],  # [ 5,  6, 10, 16 ]  # [ 1,  7,  6, 15 ]
  [ 13, 14, 15, 16 ]   # [ 9, 13, 14, 15 ]  # [ 5,  9, 13, 14 ]
]

Update v0.2

The dimension of the array is denoted: NxM where N and M can be any numbers, even or odd. For example 5x4, 4,4, 4x8 etc..

There is no such thing as "empty squares".


Solution

  • TL:DR

    If you want to jump straight to the solution code, jump to the bottom section of this answer.

    Explanation

    You need to break down the problem and solve each one independently.

    Problems

    1. Get the number of layers
    2. Loop in reverse spiral form to just get the expected values
    3. Shift them based on the rotation parameter given

    Let us walk through each point separately:


    Get the number of layers

    You need a way to get the number of layers. The below matrix has 2 layers. How?

    given a matrix:

           matrix           layers
      --------------------------------
     |  1,  2,  3,  4  |   0  0  0  0 |
     |  5,  6,  7,  8  |   0  1  1  0 |
     |  9, 10, 11, 12  |   0  1  1  0 |
     | 13, 14, 15, 16  |   0  0  0  0 |
      --------------------------------
    

    To find the number of layers, simply do:

    [rows, cols].min / 2
    

    Thus the first problem is done.


    Loop in reverse spiral form to just get the expected values

    This part requires a lot of thinking. Let us visualise:

           matrix           layers
      --------------------------------
     |  1,  2,  3,  4  |   ↓  ←  ←  ↰ |   0  0  0  0 |
     |  5,  6,  7,  8  |   ↓  1  1  ↑ |   0  ↓  ↰  0 |
     |  9, 10, 11, 12  |   ↓  1  1  ↑ |   0  ↳  →  0 |
     | 13, 14, 15, 16  |   ↳  →  →  → |   0  0  0  0 |
      --------------------------------
    

    This is achievable. We will have 4 for loops. Each loop will take care of:

    1. left (top to bottom)
    2. bottom (left to right)
    3. right (bottom to top)
    4. top (right to left)

    Before I get into the loops, we need some container to store our values in spiral form.

    Let us have a temp array to store the values:

    # this array will get us the output of borders of the layer
    row = []
    

    For the sake of explanation, let us only work on the outer most layer. (i.e. 0th layer:

    1st Loop (Left: top to bottom)

    # this loop will output the top-left side of the matrix
    # ==============================
    #  ↓ [  1,  2,  3,  4 ]
    #  ↓ [  5,  6,  7,  8 ]
    #  ↓ [  9, 10, 11, 12 ]
    #  ↓ [ 13, 14, 15, 16 ]
    # Output: [[1, 5, 9], [6] ]
    # ==============================
    (0...rows - 1 - layer).each do |i|
      row << matrix[i][layer]
    end
    

    Note: 0 means the 0th layer.

    2nd Loop (Bottom: Left to Right)

    # this loop will output the bottom side of the matrix
    # ==============================
    #  ↓ [  1,  2,  3,  4 ]
    #  ↓ [  5,  6,  7,  8 ]
    #  ↓ [  9, 10, 11, 12 ]
    #  ↓ [ 13, 14, 15, 16 ]
    #   ↪ →   →   →    →
    # Output: [[1, 5, 9, 13, 14, 15], [6, 10]]
    # ==============================
    (0...cols - 1 - layer).each do |i|
      row << matrix[rows - 1 - layer][i]
    end
    

    3rd Loop (Right: Bottom to Top)

    # this loop will output the right side of the matrix
    # ==============================
    #  ↓ [  1,  2,  3,  4 ] ↑
    #  ↓ [  5,  6,  7,  8 ] ↑
    #  ↓ [  9, 10, 11, 12 ] ↑
    #    [ 13, 14, 15, 16 ] ↑
    #   ↪  →   →   →   →  ⤻
    # Output: [[1, 5, 9, 13, 14, 15, 16, 12, 8], [6, 10, 11]]
    # ==============================
    (rows - 1 - layer).step(0 + 1, -1).each do |i|
      row << matrix[i][cols - 1 - layer]
    end
    

    4th Loop (Top: Right to Left)

    # this loop will output the top side of the matrix
    # ==============================
    #       ←   ←   ←   ←   ↰
    #  ↓ [  1,  2,  3,  4 ] ↑
    #  ↓ [  5,  6,  7,  8 ] ↑
    #  ↓ [  9, 10, 11, 12 ] ↑
    #    [ 13, 14, 15, 16 ] ↑
    #   ↪  →   →   →   →  ⤻
    # Output: [[1, 5, 9, 13, 14, 15, 16, 12, 8, 4, 3, 2], [6, 10, 11, 7]]
    # ==============================
    (cols - 1 - layer).step(0 + 1, -1).each do |i|
      row << matrix[layer][i]
    end
    

    Shift them based on the rotation parameter given

    So now at this point, we have the values in the spiral form. But the most important aspect of this problem lies in this section. How does one shift the values? Funnily enough, we will use modulo.

    The modulo will do the main thing here. It will allow us to shift values based on the rotate. But also give us the correct index in the array to start the shift. For example, if we want to rotate 2 times: 2 % 12 = 2 for the outer most layer.

    # row = [1, 5, 9, 13, 14, 15, 16, 12, 8, 4, 3, 2]
    shift = rotate % row.size
    # if we negate shift variable, we can get correct index
    # i.e. row[-2] = 3
    idx = -shift
    

    Before we shift values, let us create another matrix which will contain the correct values:

     # let us create a new matrix
     result = (1..( rows * cols )).each_slice(rows).to_a
    

    We will loop again in the same manner, but get the values from the idx in row. For example:

    (0...rows - 1 - 0).each do |i|
      result[i][layer] = row[idx]
      idx += 1
      idx %= row.size
    end
    (0...cols - 1 - 0).each do |i|
      result[rows - 1 - 0][i] = row[idx]
      idx += 1
      idx %= row.size
    end
    (rows - 1 - 0).step(0 + 1, -1).each do |i|
      result[i][cols - 1 - 0] = row[idx]
      idx += 1
      idx %= row.size
    end
    (cols - 1 - 0).step(0 + 1, -1).each do |i|
      result[0][i] = row[idx]
      idx += 1
      idx %= row.size
    end
    

    Note: 0 is the 0th layer (for the sake of explanation)

    Solution

    matrix_4_x_4 = (1..16).each_slice(4).to_a
    
    matrix_8_x_8 = (1..64).each_slice(8).to_a
    
    def matrix_rotation(*args)
    
      # let us extract rows & cols from our matrix. We also need to know how
      # times to rotate.
      rows, cols, rotate, matrix = args
    
      # to find out how many layers our matrix have, simply get the min of the two (rows, cols)
      # and divide it
      layers, str_cols = [rows, cols].min / 2, ""
    
      # needed to beatify our console output in table format
      cols.times do str_cols << "%5s " end
    
      # we will work on a temporary array
      temp_rows = []
    
      # so the first task is to loop n times, where n is the number of layers
      (0...layers).each do |layer|
    
        # this array will get us the output of borders of the layer
        row = []
    
        # this loop will output the top-left side of the matrix
        # ==============================
        #  ↓ [  1,  2,  3,  4 ]
        #  ↓ [  5,  6,  7,  8 ]
        #  ↓ [  9, 10, 11, 12 ]
        #  ↓ [ 13, 14, 15, 16 ]
        # Output: [[1, 5, 9], [6] ]
        # ==============================
        (layer...rows - 1 - layer).each do |i|
          row << matrix[i][layer]
        end
    
        # this loop will output the bottom side of the matrix
        # ==============================
        #  ↓ [  1,  2,  3,  4 ]
        #  ↓ [  5,  6,  7,  8 ]
        #  ↓ [  9, 10, 11, 12 ]
        #  ↓ [ 13, 14, 15, 16 ]
        #   ↪ →   →   →    →
        # Output: [[1, 5, 9, 13, 14, 15], [6, 10]]
        # ==============================
        (layer...cols - 1 - layer).each do |i|
          row << matrix[rows - 1 - layer][i]
        end
    
        # this loop will output the right side of the matrix
        # ==============================
        #  ↓ [  1,  2,  3,  4 ] ↑
        #  ↓ [  5,  6,  7,  8 ] ↑
        #  ↓ [  9, 10, 11, 12 ] ↑
        #    [ 13, 14, 15, 16 ] ↑
        #   ↪  →   →   →   →  ⤻
        # Output: [[1, 5, 9, 13, 14, 15, 16, 12, 8], [6, 10, 11]]
        # ==============================
        (rows - 1 - layer).step(layer + 1, -1).each do |i|
          row << matrix[i][cols - 1 - layer]
        end
    
        # this loop will output the top side of the matrix
        # ==============================
        #       ←   ←   ←   ←   ↰
        #  ↓ [  1,  2,  3,  4 ] ↑
        #  ↓ [  5,  6,  7,  8 ] ↑
        #  ↓ [  9, 10, 11, 12 ] ↑
        #    [ 13, 14, 15, 16 ] ↑
        #   ↪  →   →   →   →  ⤻
        # Output: [[1, 5, 9, 13, 14, 15, 16, 12, 8, 4, 3, 2], [6, 10, 11, 7]]
        # ==============================
        (cols - 1 - layer).step(layer + 1, -1).each do |i|
          row << matrix[layer][i]
        end
        temp_rows << row
      end
    
      # let us create a new matrix
      result = (1..( rows * cols )).each_slice(rows).to_a
    
      # we're going to loop in the same manner as before
      (0...layers).each do |layer|
    
        # based on current layer, get the values around that layer
        row = temp_rows[layer]
    
        # !important: the modulo will do the main thing here:
        # It will allow us to shift values based on the rotate. But also
        # gives us the correct index in the array to start the shift.
        # For example, if we want to rotate 2 times: 2 % 12 = 2 for the outer most layer
        shift = rotate % row.size
    
        # when whe negate the shift value, we will get the correct index from the end of the array.
        # row = [1, 5, 9, 13, 14, 15, 16, 12, 8, 4, 3, 2]
        # So -2 in row[-2] for the outer layer is 3. We increment idx, then row[-1] is 2 etc..
        idx = -shift
    
        (layer...rows - 1 - layer).each do |i|
          result[i][layer] = row[idx]
          idx += 1
          idx %= row.size
        end
        (layer...cols - 1 - layer).each do |i|
          result[rows - 1 - layer][i] = row[idx]
          idx += 1
          idx %= row.size
        end
        (rows - 1 - layer).step(layer + 1, -1).each do |i|
          result[i][cols - 1 - layer] = row[idx]
          idx += 1
          idx %= row.size
        end
        (cols - 1 - layer).step(layer + 1, -1).each do |i|
          result[layer][i] = row[idx]
          idx += 1
          idx %= row.size
        end
      end
    
      result.each do |row| printf("#{str_cols}\n", *row) end
    
    end
    
    matrix_rotation(
      matrix_8_x_8.size,
      matrix_8_x_8.first.size,
      2,
      matrix_8_x_8
    )