ruby - повернуть матрицу против часовой стрелки на n позиций - PullRequest
0 голосов
/ 20 октября 2018

Для двумерной матрицы:

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

Как мы можем повернуть матрицу против часовой стрелки, чтобы значения нажимались следующим образом?

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

Примечание

Этот вопрос не является дубликатом this & this, потому что я пытаюсь добиться, вращаязначения против часовой стрелки.

Моя текущая реализация и проблема

Моя текущая реализация выводит значения только против часовой стрелки, но не поворачивает значения.

  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

Основная идея заключается в том, что матрица должна вращаться в правильном направлении.Например, скажем, наша матрица требует 2 вращения.Поэтому:

   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

Размерность массива обозначается: NxM где N и M могут быть любыми числами, четными или нечетными.Например, 5x4, 4,4, 4x8 и т. Д.

Не существует такой вещи, как «пустые квадраты».

Ответы [ 4 ]

0 голосов
/ 23 октября 2018

Я подумал, что было бы интересно сравнить мой код с @ Humbledore's.(@iGian: я могу добавить ваш код в тест, если вы сможете отредактировать свой ответ, чтобы обернуть его в метод с аргументами matrix и nbr_rotations).

def nxt(rows, cols, row, col)
  case row     
  when rows[:first]
    col == cols[:last]  ? [row+1, col] : [row, col+1]
  when rows[:last]
    col == cols[:first] ? [row-1, col] : [row, col-1]
  else
    col == cols[:last]  ? [row+1, col] : [row-1, col]
  end
end

def cary1(matrix, n)
  arr = matrix.dup.map(&:dup)
  nrows, ncols = arr.size, arr.first.size     
  0.upto([nrows, ncols].min/2-1) do |m|
    rows = { first: m, last: nrows-m-1 }
    cols = { first: m, last: ncols-m-1 }
    rect_size = 2 * (nrows + ncols) - 8*m - 4
    rotations = n % rect_size
    row = col = rrow = rcol = m
    rotations.times { rrow, rcol = nxt(rows, cols, rrow, rcol) }
    rect_size.times do
      arr[row][col] = matrix[rrow][rcol]
      row, col   = nxt(rows, cols,  row,  col)
      rrow, rcol = nxt(rows, cols, rrow, rcol)
    end
  end
  arr
end

def first_replacement_loc(rows, cols, rotations)
  ncm1 = cols[:last]-cols[:first]       
  nrm1 = rows[:last]-rows[:first]
  return [rows[:first], cols[:first]+rotations] if rotations <= ncm1
  rotations -= ncm1
  return [rows[:first]+rotations, cols[:last]] if rotations <= nrm1
  rotations -= nrm1
  return [rows[:last], cols[:last]-rotations] if rotations <= ncm1
  rotations -= ncm1
  [rows[:last]-rotations, cols[:first]]
end

def cary2(matrix, n)
  arr = matrix.dup.map(&:dup)
  nrows, ncols = arr.size, arr.first.size     
  0.upto([nrows, ncols].min/2-1) do |m|
    rows = { first: m, last: nrows-m-1 }
    cols = { first: m, last: ncols-m-1 }
    rect_size = 2 * (nrows + ncols) - 8*m - 4
    rotations = n % rect_size
    row = col = m
    rrow, rcol = first_replacement_loc(rows, cols, rotations)
    rect_size.times do
      arr[row][col] = matrix[rrow][rcol]
      row, col   = nxt(rows, cols,  row,  col)
      rrow, rcol = nxt(rows, cols, rrow, rcol)
    end
  end
  arr
end

def humbledore(matrix, rotate)
  rows, cols = matrix.size, matrix.first.size
  layers, str_cols = [rows, cols].min / 2, ""
  # cols.times do str_cols << "%5s " end
  temp_rows = []
  (0...layers).each do |layer|
    row = []
    (layer...rows - 1 - layer).each do |i|
      row << matrix[i][layer]
    end
    (layer...cols - 1 - layer).each do |i|
      row << matrix[rows - 1 - layer][i]
    end
    (rows - 1 - layer).step(layer + 1, -1).each do |i|
      row << matrix[i][cols - 1 - layer]
    end
    (cols - 1 - layer).step(layer + 1, -1).each do |i|
      row << matrix[layer][i]
    end
    temp_rows << row
  end
  result = (1..( rows * cols )).each_slice(rows).to_a
  (0...layers).each do |layer|
    row = temp_rows[layer]
    shift = rotate % row.size
    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
end

require 'benchmark'

def test(rows, cols, rotations)
  puts "\nrows = #{rows}, cols = #{cols}, rotations = #{rotations}"
  matrix = (1..rows*cols).each_slice(cols).to_a
  Benchmark.bm do |x|
    x.report("Cary1") { cary1(matrix, rotations) }
    x.report("Cary2") { cary2(matrix, rotations) }
    x.report("Humbledore") { humbledore(matrix, rotations) }
  end
end

test 10,10,1
rows = 10, cols = 10, rotations = 1
   user         system      total        real
   Cary1       0.000000   0.000000   0.000000 (  0.000077)
   Cary2       0.000000   0.000000   0.000000 (  0.000074)
   Humbledore  0.000000   0.000000   0.000000 (  0.000051)

test 10,10,78
rows = 10, cols = 10, rotations = 78
   user         system      total        real
   Cary1       0.000000   0.000000   0.000000 (  0.000079)
   Cary2       0.000000   0.000000   0.000000 (  0.000061)
   Humbledore  0.000000   0.000000   0.000000 (  0.000053)

test 100,100,378
rows = 100, cols = 100, rotations = 378
   user         system      total        real
   Cary1       0.000000   0.000000   0.000000 (  0.007673)
   Cary2       0.015625   0.000000   0.015625 (  0.005168)
   Humbledore  0.000000   0.000000   0.000000 (  0.002919)

test 500,500,1950
rows = 500, cols = 500, rotations = 1950
   user         system      total        real
   Cary1       0.171875   0.000000   0.171875 (  0.166671)
   Cary2       0.140625   0.000000   0.140625 (  0.137141)
   Humbledore  0.046875   0.000000   0.046875 (  0.053705)

test 500,1000,2950
rows = 500, cols = 1000, rotations = 2950
   user         system      total        real
   Cary1       0.296875   0.000000   0.296875 (  0.292997)
   Cary2       0.234375   0.000000   0.234375 (  0.248384)
   Humbledore  0.125000   0.000000   0.125000 (  0.103964)

Бенчмарк сообщает о времени выполнения в секундах.Результаты оказываются весьма непротиворечивыми.

Обратите внимание, что во всех проведенных мною тестах количество столбцов массива, по крайней мере, равно количеству строк.Это связано с тем, что в коде Хамблдора возникало исключение NoMethodError (undefined method '[]=' for nil:NilClass), когда число строк превышало количество столбцов.(Попробуйте, например, test 3,2,1.) Сообщение об ошибке появилось во второй строке следующего блока кода.

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

Я ожидаю, что проблему легко устранить.

0 голосов
/ 21 октября 2018

Это еще одна реализация (я не создал метод, просто логику, которую нужно улучшить).

array = (1..24).each_slice(6).to_a
array.each { |e| p e }
puts 

n = 4 # sub matrix rows
m = 6 # sub matrix cols
x = 0 # x row origin (corner) of the rotation
y = 0 # y col origin (corner) of the rotation
rotations = 2 # negative is ccw, positive is cw

raise "Sub matrix too small, must be 2x2 at least" if m < 2 || n < 2
# to add: check if the submatrix is inside the matrix, given the origin x, y
y_size = array.size
x_size = array.size
idx_map = Array.new(n){ [] }
m.times.map { |mm| n.times.map { |nn| idx_map[nn][mm] = [nn + x, mm + y] } }
before = [(idx_map.map(&:shift)).concat(idx_map.pop).concat(idx_map.map(&:pop).reverse).concat(idx_map.shift.reverse)].flatten(1)
after = before.rotate(rotations)
tmp = array.map(&:dup)
before.size.times.map { |idx| array[before[idx][0]][before[idx][1]] = tmp[after[idx][0]][after[idx][1]]}

array.each { |e| p e }

#=> [1, 2, 3, 4, 5, 6]
#=> [7, 8, 9, 10, 11, 12]
#=> [13, 14, 15, 16, 17, 18]
#=> [19, 20, 21, 22, 23, 24]
#=> 
#=> [13, 7, 1, 2, 3, 4]
#=> [19, 8, 9, 10, 11, 5]
#=> [20, 14, 15, 16, 17, 6]
#=> [21, 22, 23, 24, 18, 12]

Вы также можете вращать подматрицу 3x3, начиная с (1, 1).), например, n = 3, m = 3, x = 1, y = 1 и rotations = -1:

#=> [1, 2, 3, 4, 5, 6]
#=> [7, 9, 10, 16, 11, 12]
#=> [13, 8, 15, 22, 17, 18]
#=> [19, 14, 20, 21, 23, 24]
0 голосов
/ 22 октября 2018

TL: DR

Если вы хотите перейти прямо к коду решения, перейдите к нижнему разделу этого ответа.

Объяснение

Необходимо разбитьЗадача и решить каждый независимо.

Задачи

  1. Получить количество слоев
  2. Цикл в обратной спиральной форме, чтобы просто получить ожидаемые значения
  3. Сдвиньте их в зависимости от заданного параметра вращения

Давайте пройдемся по каждой точке отдельно:


Получим количество слоев

Вам нуженспособ получить количество слоев.В приведенной ниже матрице есть 2 слоя.Как?

с учетом матрицы:

       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 |
  --------------------------------

Чтобы найти количество слоев, просто выполните:

[rows, cols].min / 2

Таким образом, первая проблема решена.


Цикл в обратной спиральной форме, чтобы просто получить ожидаемые значения

Эта часть требует много размышлений.Давайте представим:

       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 |
  --------------------------------

Это достижимо.У нас будет 4 for петель.Каждая петля будет заботиться о:

  1. слева (сверху вниз)
  2. снизу (слева направо)
  3. справа (снизу вверх)
  4. сверху (справа налево)

Прежде чем попасть в петли, нам нужнонекоторый контейнер для хранения наших значений в спиральной форме.

Давайте иметь временный массив для хранения значений:

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

Для пояснения, давайте работать только над самым внешнимслой.(т. е. 0-й слой:

1-й цикл (слева: сверху вниз)

# 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

Примечание: 0 означает 0-й слой.

2-й цикл (снизу: слева направо)

# 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

3-й цикл (справа: снизу вверх)

# 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

4-й цикл (сверху: справа налево)

# 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

Сдвиньте их, основываясь на заданном параметре вращения

Итак, на данный момент у нас есть значения в спиральной форме, но самый важный аспект этой проблемы лежит в этом разделе.Как можно сдвинуть значения? Как ни странно, мы будем использовать модуль.

Модуль будет делать здесь главное. Это позволит нам сдвигать значения на основе поворота. Но также даст нам правильный индекс вмассив для начала сдвига. Например, если мы хотим повернуть 2 раза: 2% 12 = 2 для самого внешнего слоя.

# 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

Прежде чем мы сместим значения, давайте создадим другую матрицу, которая будетсодержат правильные значения:

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

Мы повторим цикл таким же образом, но получим значения из tон idx в row.Например:

(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

Примечание: 0 - это 0-й слой (для пояснения)

Решение

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
)
0 голосов
/ 20 октября 2018

Код

def nxt(rows, cols, row, col)
  case row     
  when rows[:first]
    col == cols[:last]  ? [row+1, col] : [row, col+1]
  when rows[:last]
    col == cols[:first] ? [row-1, col] : [row, col-1]
  else
    col == cols[:last]  ? [row+1, col] : [row-1, col]
  end
end

def rotate_array_times(matrix, n)
  arr = matrix.dup.map(&:dup)
  nrows, ncols = arr.size, arr.first.size     
  0.upto([nrows, ncols].min/2-1) do |m|
    rows = { first: m, last: nrows-m-1 }
    cols = { first: m, last: ncols-m-1 }
    rect_size = 2 * (nrows + ncols) - 8*m - 4
    rotations = n % rect_size
    row = col = rrow = rcol = m
    rotations.times { rrow, rcol = nxt(rows, cols, rrow, rcol) }
    rect_size.times do
      arr[row][col] = matrix[rrow][rcol]
      row, col   = nxt(rows, cols,  row,  col)
      rrow, rcol = nxt(rows, cols, rrow, rcol)
    end
  end
  arr
end

Примеры

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

(1..3).each { |n| p rotate_array_times(matrix, n) }
  [[2,  3,  4,  8],
   [1,  7, 11, 12],
   [5,  6, 10, 16],
   [9, 13, 14, 15]]

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

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

matrix = (1..24).each_slice(4).to_a
  #=> [[ 1,  2,  3,  4],
  #    [ 5,  6,  7,  8],
  #    [ 9, 10, 11, 12],
  #    [13, 14, 15, 16],
  #    [17, 18, 19, 20],
  #    [21, 22, 23, 24]]
(1..3).each { |n| p rotate_array_times(matrix, n) }
  #=> [[ 2,  3,  4,  8],
  #    [ 1,  7, 11, 12],
  #    [ 5,  6, 15, 16],
  #    [ 9, 10, 19, 20],
  #    [13, 14, 18, 24],
  #    [17, 21, 22, 23]]

  #   [[ 3,  4,  8, 12],
  #    [ 2, 11, 15, 16],
  #    [ 1,  7, 19, 20],
  #    [ 5,  6, 18, 24],
  #    [ 9, 10, 14, 23],
  #    [13, 17, 21, 22]]

  #   [[ 4,  8, 12, 16],
  #    [ 3, 15, 19, 20],
  #    [ 2, 11, 18, 24],
  #    [ 1,  7, 14, 23],
  #    [ 5,  6, 10, 22],
  #    [ 9, 13, 17, 21]]

matrix = (1..48).each_slice(8).to_a
  #=> [[ 1,  2,  3,  4,  5,  6,  7,  8],
  #    [ 9, 10, 11, 12, 13, 14, 15, 16],
  #    [17, 18, 19, 20, 21, 22, 23, 24],
  #    [25, 26, 27, 28, 29, 30, 31, 32],
  #    [33, 34, 35, 36, 37, 38, 39, 40],
  #    [41, 42, 43, 44, 45, 46, 47, 48]]

(1..3).each { |n| p rotate_array_times(matrix, n) }
[[ 2,  3,  4,  5,  6,  7,  8, 16],
 [ 1, 11, 12, 13, 14, 15, 23, 24],
 [ 9, 10, 20, 21, 22, 30, 31, 32],
 [17, 18, 19, 27, 28, 29, 39, 40],
 [25, 26, 34, 35, 36, 37, 38, 48],
 [33, 41, 42, 43, 44, 45, 46, 47]]

[[ 3,  4,  5,  6,  7,  8, 16, 24], 
 [ 2, 12, 13, 14, 15, 23, 31, 32],
 [ 1, 11, 21, 22, 30, 29, 39, 40],
 [ 9, 10, 20, 19, 27, 28, 38, 48],
 [17, 18, 26, 34, 35, 36, 37, 47], 
 [25, 33, 41, 42, 43, 44, 45, 46]]

[[ 4,  5,  6,  7,  8, 16, 24, 32],
 [ 3, 13, 14, 15, 23, 31, 39, 40],
 [ 2, 12, 22, 30, 29, 28, 38, 48],
 [ 1, 11, 21, 20, 19, 27, 37, 47],
 [ 9, 10, 18, 26, 34, 35, 36, 46],
 [17, 25, 33, 41, 42, 43, 44, 45]]

Пояснение

nxt

С учетом индексов строк и столбцов row и col, nxt(rows, cols, row, col) возвращает индексы[next_row, next_col] «следующего» элемента по периметру подрешетки, который должен заменить элемент (также по периметру) с индексами [row, col] за одну итерацию.Подмассив задается хэшами rows и cols, каждый из которых имеет ключи :first и :last.

. Рассмотрим массив arr с 4 элементами (строками), каждый элемент (строка), имеющая 6 значений (столбцы).Тогда

nrows, ncols = arr.size, arr.first.size
  #=> [4, 6]

Если m = 0

rows = { first: m, last: nrows-m-1 }
  #=> {:first=>0, :last=>3}
cols = { first: m, last: ncols-m-1 }
  #=> {:first=>0, :last=>5}

Видно, что rows и cols описывают "периметр" массива matrix.Мы можем видеть, как nxt работает следующим образом.

first_row, first_col = rows[:first], cols[:first]
row, col = first_row, first_col
print "[#{row}, #{col}]"
loop do
  next_row, next_col = nxt(rows, cols, row, col)
  print "->[#{next_row}, #{next_col}]"
  row, col = next_row, next_col
  (puts; break) if [row, col] == [first_row, first_col]
end
[0, 0]->[0, 1]->[0, 2]->[0, 3]->[0, 4]->[0, 5]->[1, 5]->[2, 5]->[3, 5]->
[3, 4]->[3, 3]->[3, 2]->[3, 1]->[3, 0]->[2, 0]->[1, 0]->[0, 0]

Если m = 1, приведенный выше расчет дает

[1, 1]->[1, 2]->[1, 3]->[1, 4]->[2, 4]->[2, 3]->[2, 2]->[2, 1]->[1, 1]

rotate_array_times

Этот метод создает глубокую копию matrix, arrr, элементы которой вращаются в предписанном веществе n раз, а затем возвращает полученный массив.

Для ускорения вычислений nзаменяется модулем себя.Например, для массива 4x4 после 12 итераций периметр массива вернется к своему первоначальному значению.Поэтому достаточно выполнить n % 12 поворотов.

matrix содержит n = [matrix.size, matrix.first.size].min подмассивы, периметры которых должны вращаться.Верхний левый угол каждого подмассива задается координатой [m,m], где m = 0..n-1.

Для подмассива, заданного m, первым шагом является определение местоположения элемента matrix то есть заменить элемент arr на [m,m].Это делается в строке

rotations.times { rrow, rcol = nxt(rows, cols, rrow, rcol) }

("rrow" и "rcol" для «строки замены» и «столбца замены» соответственно).В это время элемент arr в местоположении row #=> m, col #=> m должен быть заменен элементом matrix в местоположении, заданном rrow и rcol.Затем выполняются следующие операции столько раз, сколько элементов в периметре подмассива должны вращаться:

arr[row][col] = matrix[rrow][rcol]
row, col   = nxt(rows, cols,  row,  col)
rrow, rcol = nxt(rows, cols, rrow, rcol)

Эффективность настройки

Скромное улучшениеэффективности можно достичь, заменив строку

rotations.times { rrow, rcol = nxt(rows, cols, rrow, rcol) }

на

rrow, rcol = first_replacement_loc(rows, cols, rotations)

и добавив следующий метод.

def first_replacement_loc(rows, cols, rotations)
  ncm1 = cols[:last]-cols[:first]       
  nrm1 = rows[:last]-rows[:first]
  return [rows[:first], cols[:first]+rotations] if rotations <= ncm1
  rotations -= ncm1
  return [rows[:first]+rotations, cols[:last]] if rotations <= nrm1
  rotations -= nrm1
  return [rows[:last], cols[:last]-rotations] if rotations <= ncm1
  rotations -= ncm1
  [rows[:last]-rotations, cols[:first]]
end
...