Вращение двумерного массива в Python - PullRequest
93 голосов
/ 07 декабря 2011

В программе, которую я пишу, возникла необходимость вращать двумерный массив. В поисках оптимального решения я нашел этот впечатляющий однострочник, который выполняет свою работу:

rotated = zip(*original[::-1])

Я сейчас использую его в своей программе, и он работает как положено. Однако моя проблема в том, что я не понимаю, как это работает.

Буду признателен, если кто-нибудь сможет объяснить, как различные задействованные функции достигают желаемого результата.

Ответы [ 7 ]

76 голосов
/ 07 декабря 2011

Это умный бит.Вот разбивка:

  • [::-1] - делает мелкую копию оригинального списка в обратном порядке.Можно также использовать reversed(), который будет производить обратный итератор по списку, а не копировать его фактически (более эффективно использовать память).
  • * - делает каждый подсписок в исходном списке отдельным аргументом для zip() (то есть распаковывает список)
  • zip() - берет один элемент из каждого аргумента и составляет список (ну, кортеж) из них и повторяет до тех пор, пока не исчерпаны все подсписки.Вот где в действительности происходит транспонирование.

Итак, предположим, что у вас есть это:

[ [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9] ]

Сначала вы получите это (мелкая, перевернутая копия):

[ [7, 8, 9],
  [4, 5, 6],
  [1, 2, 3] ]

Далее каждый из подсписков передается в качестве аргумента zip:

zip([7, 8, 9], [4, 5, 6], [1, 2, 3])

zip() многократно потребляет один элемент в начале каждого из своих аргументов и делает из него кортеж до тех пор, покабольше нет предметов, в результате чего:

[(7, 4, 1), 
 (8, 5, 2), 
 (9, 6, 3)]

И Боб - твой дядя.

Чтобы ответить на вопрос @ IkeMiguel в комментарии о его вращении в другом направлении, это довольно просто: вы простонужно поменять обе последовательности, которые входят в zip, и результат.Первое может быть достигнуто путем удаления [::-1], а второе - путем разбрасывания reversed() вокруг всего объекта.Так как reversed() возвращает итератор по списку, нам нужно поместить list() вокруг , что , чтобы преобразовать его.Итак:

rotated = list(zip(*reversed(original)))

Конечно, вы также можете просто повернуть список по часовой стрелке три раза.: -)

73 голосов
/ 07 декабря 2011

Рассмотрим следующий двумерный список:

original = [[1, 2],
            [3, 4]]

Давайте разберем это шаг за шагом:

>>> original[::-1]   # elements of original are reversed
[[3, 4], [1, 2]]

Этот список передается в zip() с использованием аргумента распаковки , поэтому вызов zip в итоге будет эквивалентен этому:

zip([3, 4],
    [1, 2])
#    ^  ^----column 2
#    |-------column 1
# returns [(3, 1), (4, 2)], which is a original rotated clockwise

Надеемся, что комментарии проясняют, что делает zip, он будет группировать элементы из каждого входного элемента, повторяемого на основе индекса, или другими словами, он группирует столбцы.

14 голосов
/ 07 декабря 2011

Есть три части:

  1. original [:: - 1] инвертирует исходный массив. Это обозначение является разрезанием списка Python. Это дает вам «подсписок» исходного списка, описанного [start: end: step], start - первый элемент, end - последний элемент, который будет использоваться в подсписке. шаг говорит, что каждый шаг шаг элемент от первого до последнего. Пропущенные начало и конец означают, что срез будет всем списком, а отрицательный шаг означает, что вы получите элементы в обратном порядке. Так, например, если оригинал был [x, y, z], результат был бы [z, y, x]
  2. * Когда перед списком / кортежем в списке аргументов вызова функции стоит «развернуть» список / кортеж, чтобы каждый из его элементов стал отдельным аргументом функции, а не самим списком / кортежем. Так что если, скажем, args = [1,2,3], то zip (args) совпадает с zip ([1,2,3]), а zip (* args) совпадает с zip (1, 2,3).
  3. zip - это функция, которая принимает n аргументов, каждый из которых имеет длину m, и создает список длиной m, элементы которого имеют длину n и содержат соответствующие элементы каждого из исходных списков. Например, zip ([1,2], [a, b], [x, y]) есть [[1, a, x], [2, b, y]]. Смотрите также Документация Python.
7 голосов
/ 25 января 2018

Просто наблюдение. Входные данные представляют собой список списков, но вывод из очень хорошего решения: rotated = zip (* original [:: - 1]) возвращает список кортежей.

Это может быть или не быть проблемой.

Однако это легко исправить:

original = [[1, 2, 3],
            [4, 5, 6],
            [7, 8, 9]
            ]


def rotated(array_2d):
    list_of_tuples = zip(*array_2d[::-1])
    return [list(elem) for elem in list_of_tuples]
    # return map(list, list_of_tuples)

print(list(rotated(original)))

# [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

Компонент списка или карта преобразуют внутренние кортежи обратно в списки.

1 голос
/ 23 февраля 2018
def ruota_antiorario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento) for elemento in ruota]
def ruota_orario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento)[::-1] for elemento in ruota][::-1]
1 голос
/ 14 марта 2017

Я сам столкнулся с этой проблемой и нашел отличную страницу википедии по этому вопросу (в параграфе «Общие вращения»:
https://en.wikipedia.org/wiki/Rotation_matrix#Ambiguities

Затем я написал следующий код, супер многословный, чтобы иметь четкое представление о том, что происходит.

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

Чтобы быстро проверить это, вы можете скопировать / вставить его здесь:
http://www.codeskulptor.org/

triangle = [[0,0],[5,0],[5,2]]
coordinates_a = triangle[0]
coordinates_b = triangle[1]
coordinates_c = triangle[2]

def rotate90ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1]
# Here we apply the matrix coming from Wikipedia
# for 90 ccw it looks like:
# 0,-1
# 1,0
# What does this mean?
#
# Basically this is how the calculation of the new_x and new_y is happening:
# new_x = (0)(old_x)+(-1)(old_y)
# new_y = (1)(old_x)+(0)(old_y)
#
# If you check the lonely numbers between parenthesis the Wikipedia matrix's numbers
# finally start making sense.
# All the rest is standard formula, the same behaviour will apply to other rotations, just
# remember to use the other rotation matrix values available on Wiki for 180ccw and 170ccw
    new_x = -old_y
    new_y = old_x
    print "End coordinates:"
    print [new_x, new_y]

def rotate180ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1] 
    new_x = -old_x
    new_y = -old_y
    print "End coordinates:"
    print [new_x, new_y]

def rotate270ccw(coordinates):
    print "Start coordinates:"
    print coordinates
    old_x = coordinates[0]
    old_y = coordinates[1]  
    new_x = -old_x
    new_y = -old_y
    print "End coordinates:"
    print [new_x, new_y]

print "Let's rotate point A 90 degrees ccw:"
rotate90ccw(coordinates_a)
print "Let's rotate point B 90 degrees ccw:"
rotate90ccw(coordinates_b)
print "Let's rotate point C 90 degrees ccw:"
rotate90ccw(coordinates_c)
print "=== === === === === === === === === "
print "Let's rotate point A 180 degrees ccw:"
rotate180ccw(coordinates_a)
print "Let's rotate point B 180 degrees ccw:"
rotate180ccw(coordinates_b)
print "Let's rotate point C 180 degrees ccw:"
rotate180ccw(coordinates_c)
print "=== === === === === === === === === "
print "Let's rotate point A 270 degrees ccw:"
rotate270ccw(coordinates_a)
print "Let's rotate point B 270 degrees ccw:"
rotate270ccw(coordinates_b)
print "Let's rotate point C 270 degrees ccw:"
rotate270ccw(coordinates_c)
print "=== === === === === === === === === "
0 голосов
/ 10 июня 2013

Я практиковал понимание вложенного списка Python и кодировал следующее, чтобы получить список столбцов в матрице, что аналогично вращению матрицы:

def getColumns(matrix):
  columns = [[row[col] for row in matrix] for col in range(len(matrix[1]))]
  return columns
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...