Обертання двовимірного масиву в Python


122

У програмі я пишу про необхідність обертати двовимірний масив. Шукаючи оптимальне рішення, я знайшов цей вражаючий одноколісний лайнер, який виконує цю роботу:

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

Я зараз його використовую у своїй програмі, і вона працює як слід. Моя проблема, однак, полягає в тому, що я не розумію, як це працює.

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


7
Справді. Я знайшов це в цьому питанні.
paldepind

Відповіді:


96

Розглянемо наступний двовимірний список:

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це робить, він згрупує елементи з кожного вхідного тексту, який можна перетворити на основі індексу, або іншими словами, він групує стовпці.


2
Близький. Але я вибрав твій завдяки акуратному мистецтву ASCII;)
paldepind

1
а зірочка ??
John ktejik

@johnktejik - ось відповідь "розпакування аргументу", для деталей натисніть посилання
JR Heard

1
Для наочності слід зазначити, що це обертає матрицю за годинниковою стрілкою і що списки з оригіналу перетворюються на кортежі.
Еверетт

1
йти повне коло (отримати назад список списків і не кортежі) Я зробив це:rotated = [list(r) for r in zip(*original[::-1])]
матовий

94

Це розумний шматочок.

По-перше, як зазначається в коментарі, в Python 3 zip()повертається ітератор, тому вам потрібно вкласти це все, list()щоб повернути фактичний список, так що до 2020 року це фактично:

list(zip(*original[::-1]))

Ось поділка:

  • [::-1]- робить дрібну копію оригінального списку у зворотному порядку. Можливо також використовувати, reversed()який створював би зворотний ітератор над списком, а не фактично копіював список (більш ефективний об'єм пам'яті).
  • *- робить кожен підпис у вихідному списку окремим аргументом zip()(тобто розпаковує список)
  • zip()- бере один елемент з кожного аргументу і складає список (ну, кортеж) з них і повторюється, поки не будуть вичерпані всі підлісти. Ось де насправді відбувається транспозиція.
  • list()перетворює вихідний 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()навколо цього, щоб перетворити його. З кількома додатковими list()дзвінками для перетворення ітераторів у фактичний список. Так:

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

Ми можемо трохи спростити це, використовуючи фрагмент "Марсіанський смайлик", а не reversed()... тоді зовнішнє нам не потрібно list():

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

Звичайно, ви також можете просто повернути список три рази за годинниковою стрілкою. :-)


2
Чи можна обертати проти годинникової стрілки ??
Мігель Айк

@MiguelIke так, зробіть zip (* matrix) [:: - 1]
RYS

3
^ зауважте, що вам потрібно подати результат zipдо списку в Python 3.x!
RYS

17

Це три частини:

  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.

+1, оскільки ти єдиний, мабуть, пояснює перший крок.
paldepind

8

Просто спостереження. Вхід - це список списків, але вихід із дуже приємного рішення: 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]]

Комп'ютер списку або карта перетворюють внутрішні кортежі назад у списки.


2
def ruota_orario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento) for elemento in ruota]
def ruota_antiorario(matrix):
   ruota=list(zip(*reversed(matrix)))
   return[list(elemento)[::-1] for elemento in ruota][::-1]

4
Поясніть, будь ласка, ваше рішення, щоб інші могли його краще зрозуміти.
HelloSpeakman

звичайно, перша функція (ruota_antiorario) обертається проти годинникової стрілки, а друга функція (ruota_orario) за годинниковою стрілкою
user9402118

1

У мене була ця проблема сама, і я знайшов чудову сторінку вікіпедії на цю тему (у пункті "Загальні повороти":
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 "=== === === === === === === === === "

-1

Обертання лічильника за годинниковою стрілкою (стандартний стовпчик для зведення рядків) у вигляді списку та Dict

rows = [
  ['A', 'B', 'C', 'D'],
  [1,2,3,4],
  [1,2,3],
  [1,2],
  [1],
]

pivot = []

for row in rows:
  for column, cell in enumerate(row):
    if len(pivot) == column: pivot.append([])
    pivot[column].append(cell)

print(rows)
print(pivot)
print(dict([(row[0], row[1:]) for row in pivot]))

Виробляє:

[['A', 'B', 'C', 'D'], [1, 2, 3, 4], [1, 2, 3], [1, 2], [1]]
[['A', 1, 1, 1, 1], ['B', 2, 2, 2], ['C', 3, 3], ['D', 4]]
{'A': [1, 1, 1, 1], 'B': [2, 2, 2], 'C': [3, 3], 'D': [4]}

1
Це не пов'язане з питанням, яке вимагає пояснення, як zip(*original[::-1])працює.
kaya3
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.